24 Commits

Author SHA1 Message Date
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
60 changed files with 12471 additions and 251 deletions

View File

@@ -1,8 +1,22 @@
<!-- file: CHANGELOG.md --> <!-- file: CHANGELOG.md -->
<!-- version: 31 --> <!-- version: 32 -->
# Changelog KSP # Changelog KSP
## 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 — 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. `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 # file: Cargo.toml
# version: 518 # version: 539
[workspace] [workspace]
resolver = "3" resolver = "3"
members = ["crates/ksp-app-backfill-desk", "crates/ksp-app-config-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-store-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-job-api", "crates/ksp-job-backfill-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-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"] 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] [workspace.package]
version = "0.3.11" version = "0.3.12"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"

View File

@@ -1,5 +1,5 @@
<!-- file: README.md --> <!-- file: README.md -->
<!-- version: 12 --> <!-- version: 13 -->
# Khadhroony Solana Project # 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-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, et possède maintenant une première source productive Yellowstone + hydration HTTP `getTransaction`. Les signaux `Transaction`, `TransactionStatus` et transactions de `Block` sont coalescés puis hydratés avant admission RAW ; `BlockMeta` et `Slot` restent des signaux de continuité. 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 ## Points d'entrée

View File

@@ -1,5 +1,5 @@
<!-- file: ROADMAP.md --> <!-- file: ROADMAP.md -->
<!-- version: 109 --> <!-- version: 110 -->
# Roadmap KSP # Roadmap KSP
@@ -104,7 +104,7 @@ 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.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.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`. - [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. - [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.
- [ ] `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.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.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. - [ ] `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.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/grpc_stream.rs // file: crates/ksp-onchain-transport-lib/src/grpc_stream.rs
// version: 3 // version: 4
use tonic_prost::prost::Message; // rust-rules: trait-import use tonic_prost::prost::Message; // rust-rules: trait-import
@@ -39,6 +39,43 @@ pub struct YellowstoneGrpcSubscribeSnapshot {
terminal_error_code: std::option::Option<ksp_core_lib::ErrorCode>, 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 { impl YellowstoneGrpcSubscribeSnapshot {
const fn new(initial_from_slot: std::option::Option<u64>) -> Self { const fn new(initial_from_slot: std::option::Option<u64>) -> Self {
return Self { return Self {
@@ -154,6 +191,12 @@ impl SolanaYellowstoneGrpcSubscribeSession {
return *self.snapshot_rx.borrow(); 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. /// 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 /// The mutation is rejected synchronously when local validation fails, the encoded request exceeds the configured outbound bound, the bounded request

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/lib.rs // file: crates/ksp-onchain-transport-lib/src/lib.rs
// version: 45 // version: 46
#![warn(missing_docs)] #![warn(missing_docs)]
#![deny(unreachable_pub)] #![deny(unreachable_pub)]
@@ -143,6 +143,8 @@ pub use self::grpc_settings::YellowstoneGrpcTransportSettings;
pub use self::grpc_stream::SolanaYellowstoneGrpcSubscribeSession; pub use self::grpc_stream::SolanaYellowstoneGrpcSubscribeSession;
/// Safe Yellowstone reconnect/replay continuity snapshot. /// Safe Yellowstone reconnect/replay continuity snapshot.
pub use self::grpc_stream::YellowstoneGrpcSubscribeSnapshot; 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. /// Safe Yellowstone bidirectional Subscribe lifecycle state.
pub use self::grpc_stream::YellowstoneGrpcSubscribeState; pub use self::grpc_stream::YellowstoneGrpcSubscribeState;
/// One validated standard Yellowstone account predicate. /// One validated standard Yellowstone account predicate.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/tests/public_api.rs // file: crates/ksp-onchain-transport-lib/tests/public_api.rs
// version: 50 // version: 51
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract. //! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
@@ -1013,6 +1013,10 @@ 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 _snapshot = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot>();
let _state = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeState::Reconnecting; let _state = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeState::Reconnecting;
let _session_snapshot = ksp_onchain_transport_lib::SolanaYellowstoneGrpcSubscribeSession::snapshot; 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 _reconnect_count = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot::reconnect_count;
let _gap_count = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot::continuity_gap_count; let _gap_count = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot::continuity_gap_count;
let _duplicate_count = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot::duplicate_update_count; let _duplicate_count = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot::duplicate_update_count;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs // file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs
// version: 42 // version: 43
//! Release-level completeness canaries for staged HTTP and WebSocket Transport coverage. //! 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 [ for required in [
"YellowstoneGrpcSubscribeState::Reconnecting", "YellowstoneGrpcSubscribeState::Reconnecting",
"YellowstoneGrpcSubscribeSnapshot", "YellowstoneGrpcSubscribeSnapshot",
"YellowstoneGrpcSubscribeSnapshotSource",
"snapshot_source",
"wait_for_change",
"reconnect_subscribe_stream", "reconnect_subscribe_stream",
"replay_first_available", "replay_first_available",
"SubscribeReplayInfo", "SubscribeReplayInfo",

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/grpc_stream.rs // file: crates/ksp-onchain-transport-lib/unit_tests/grpc_stream.rs
// version: 4 // version: 5
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
enum FixtureMode { enum FixtureMode {
@@ -159,6 +159,9 @@ impl yellowstone_grpc_proto::geyser::geyser_server::Geyser for FixtureGeyser {
}, },
} }
}); });
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))); return std::result::Result::Ok(tonic::Response::new(super::MpscStream::new(outbound_rx)));
} }
@@ -662,6 +665,45 @@ async fn yellowstone_reconnect_budget_exhaustion_is_terminal_and_safe() {
server.stop().await; 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")] #[tokio::test(flavor = "current_thread")]
async fn yellowstone_shutdown_interrupts_reconnect_backoff_and_mutation_is_rejected_during_reconnect() { async fn yellowstone_shutdown_interrupts_reconnect_backoff_and_mutation_is_rejected_during_reconnect() {
let server = FixtureServer::start(FixtureMode::ReconnectReplay).await; let server = FixtureServer::start(FixtureMode::ReconnectReplay).await;

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-raw-transaction-lib/README.md --> <!-- file: crates/ksp-raw-transaction-lib/README.md -->
<!-- version: 1 --> <!-- version: 2 -->
# ksp-raw-transaction-lib # ksp-raw-transaction-lib
@@ -26,6 +26,7 @@ La canonicalisation produit un `ksp_store_api::RawTransaction` avec payload KSP
### Signature Solana ### Signature Solana
- `parse_raw_transaction_signature` valide et décode une signature Base58 bornée vers exactement 64 bytes ; - `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 ; - `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. - les bornes textuelles publiques permettent l'admission avant décodage.

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-raw-transaction-lib/USAGE.md --> <!-- file: crates/ksp-raw-transaction-lib/USAGE.md -->
<!-- version: 1 --> <!-- version: 2 -->
# Utilisation de ksp-raw-transaction-lib # 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. 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 : Lorsqu'un wire Base64 complet contient déjà son tableau de signatures, utiliser :
```rust ```rust

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-raw-transaction-lib/src/lib.rs // file: crates/ksp-raw-transaction-lib/src/lib.rs
// version: 3 // version: 4
#![warn(missing_docs)] #![warn(missing_docs)]
#![deny(unreachable_pub)] #![deny(unreachable_pub)]
@@ -7,7 +7,7 @@
//! Source-neutral canonicalization of complete Solana transactions into KSP RAW transaction v1. //! 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 //! 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 //! producer-owned observation. It owns no Transport, Config, Job, Worker, async runtime or Store
//! backend behavior. //! 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; pub use self::signature::MIN_RAW_TRANSACTION_SIGNATURE_TEXT_BYTES;
/// Extracts the first canonical 64-byte Solana signature from one complete Base64 transaction wire. /// 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; 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. /// Parses one bounded Base58 Solana transaction signature to exactly 64 canonical bytes.
pub use self::signature::parse_raw_transaction_signature; pub use self::signature::parse_raw_transaction_signature;
/// One source-neutral v0 address-table lookup. /// One source-neutral v0 address-table lookup.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-raw-transaction-lib/src/signature.rs // file: crates/ksp-raw-transaction-lib/src/signature.rs
// version: 3 // version: 4
use base64::Engine; // rust-rules: trait-import 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. /// Minimum UTF-8 byte length admitted for one textual Base58 Solana transaction signature.
pub const MIN_RAW_TRANSACTION_SIGNATURE_TEXT_BYTES: usize = 64; 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. /// 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> { pub fn parse_raw_transaction_signature(value: &str) -> ksp_core_lib::Result<ksp_store_api::RawTransactionSignature> {
let text = value.as_bytes(); let text = value.as_bytes();

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-raw-transaction-lib/tests/public_api.rs // 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. //! 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; 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 // file: crates/ksp-raw-transaction-lib/unit_tests/signature.rs
// version: 3 // version: 4
use base64::Engine; // rust-rules: trait-import 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()); 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 # file: crates/ksp-worker-raw-transaction-ingest-lib/Cargo.toml
# version: 1 # version: 2
[package] [package]
name = "ksp-worker-raw-transaction-ingest-lib" name = "ksp-worker-raw-transaction-ingest-lib"
@@ -10,6 +10,7 @@ repository.workspace = true
[dependencies] [dependencies]
ksp-core-lib = { path = "../ksp-core-lib" } ksp-core-lib = { path = "../ksp-core-lib" }
ksp-logging-lib = { path = "../ksp-logging-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-raw-transaction-lib = { path = "../ksp-raw-transaction-lib" }
ksp-store-lib = { path = "../ksp-store-lib", default-features = false } ksp-store-lib = { path = "../ksp-store-lib", default-features = false }
ksp-worker-api = { path = "../ksp-worker-api" } ksp-worker-api = { path = "../ksp-worker-api" }

View File

@@ -1,65 +1,147 @@
<!-- file: crates/ksp-worker-raw-transaction-ingest-lib/README.md --> <!-- file: crates/ksp-worker-raw-transaction-ingest-lib/README.md -->
<!-- version: 1 --> <!-- version: 2 -->
# ksp-worker-raw-transaction-ingest-lib # 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 crate possède deux niveaux publics complémentaires :
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 à :
```text ```text
RawNetworkId RawTransactionIngestWorker::start
WorkerId -> fondation source-neutral, sans source productive
Worker kind = raw_transaction_ingest
RawTransactionIngestWorker::start_with_runtime_resources
-> même runtime + une source Yellowstone productive supervisée
+ hydration HTTP getTransaction
``` ```
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 : La première verticale live est :
```text ```text
RawTransactionIngestWorker::start(settings, Arc<Store>) Yellowstone standard subscribe
-> RawTransactionIngestHandle -> Transaction / TransactionStatus / Block
-> signal source-neutral (network, signature, slot, commitment, provenance sûre)
-> coalescence bornée par (network, signature, commitment)
-> HTTP getTransaction observed
-> 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 contient exactement une source Yellowstone validée. Il n'expose ni enum provider, ni collection de sources, ni callback, ni queue d'enqueue, ni client inférieur.
## 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 : `RawTransactionIngestHandle` permet de :
- demander un stop coopératif et idempotent ; - demander un stop coopératif et idempotent ;
- obtenir une source de snapshots concrets latest-value ; - lire une source de snapshots concrets latest-value ;
- utiliser cette même source via `WorkerSnapshotSource` ; - utiliser la même source via `WorkerSnapshotSource` ;
- attendre le terminal après drain/abort+join des tâches possédées. - 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`. Les tâches source, hydration et persistence possédées sont drainées ou abort+join avant publication terminale. 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 : La source Yellowstone possède également un coordinateur d'hydration borné :
1. vérifié contre le réseau attendu ; ```text
2. canonicalisé exclusivement par `ksp-raw-transaction-lib` ; in-flight hydration <= persistence_concurrency
3. associé à une observation key déterministe sous le domaine `ksp.raw_transaction_ingest.observation.v1` ; pending source signals <= 65_536
4. assemblé en acquisition RAW commune ; ```
5. remis à la persistence Store.
La crate ne possède pas un second format RAW et ne duplique pas le canonicaliseur commun. Les signaux partageant le même `(network, signature, commitment)` sont coalescés avant le fan-out HTTP. Les provenances utiles restent néanmoins conservées pour les ingress produits après hydration.
Les retries/reroutages HTTP appartiennent à `ksp-onchain-transport-lib`. Le Worker ne possède pas une seconde boucle de retry autour de `getTransaction`.
## 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 et continuité
Le reconnect/replay Yellowstone appartient à Transport. Le Worker n'écrit pas `from_slot` et n'interprète pas directement `SubscribeReplayInfo`.
Le snapshot Worker projette uniquement des informations sûres :
```text
source_state
source_reconnect_total
source_replay_attempt_total
source_continuity_gap_total
```
`RawTransactionIngestSourceState` distingue :
```text
Active
Reconnecting
Closing
Closed
Failed
```
Un replay attempt n'est pas une preuve de replay réussi ni de continuité parfaite. Si Transport augmente son compteur de continuity gap parce que la borne de rétention prouve que le slot demandé n'est plus rejouable, le Worker classe la source en failure et termine avec `worker_raw_transaction_ingest.source_failed`.
Le Worker ne lance alors ni Job Backfill ni campagne historique automatique.
## Persistence Store ## 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 ```text
entity inserted entity inserted
@@ -72,21 +154,13 @@ content conflict
store failure 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 : Les codes Worker publics sont :
- 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 :
```text ```text
worker_raw_transaction_ingest.settings_invalid worker_raw_transaction_ingest.settings_invalid
@@ -98,26 +172,7 @@ worker_raw_transaction_ingest.source_failed
worker_raw_transaction_ingest.drain_timeout worker_raw_transaction_ingest.drain_timeout
``` ```
Les diagnostics ne recopient pas de payload RAW, URL, credential, signature hostile ou texte backend/provider arbitraire. Les diagnostics et `Debug` ne recopient pas de payload RAW, signature, URL, credential, filtre provider, texte backend/provider arbitraire ou client inférieur.
## 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.
## Dépendances ## Dépendances
@@ -126,6 +181,7 @@ Les dépendances normales sont exactement :
```text ```text
ksp-core-lib ksp-core-lib
ksp-logging-lib ksp-logging-lib
ksp-onchain-transport-lib
ksp-raw-transaction-lib ksp-raw-transaction-lib
ksp-store-lib (default-features = false) ksp-store-lib (default-features = false)
ksp-worker-api ksp-worker-api
@@ -133,25 +189,25 @@ sha2
tokio (macros, rt, sync, time) 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 ; - plusieurs sources productives simultanées dans `RawTransactionIngestRuntimeResources` ;
- sélection Config de sources/endpoints/credentials ; - source WS/Helius-specific ou HTTP polling Worker ;
- discovery/hydration/replay de continuité live ; - sélection Config interne au Worker ;
- hot reconfiguration de listeners/sources ; - checkpoint persistent de processing frontier ;
- campagne de réparation historique automatique ;
- application Desk ou process autonome ; - application Desk ou process autonome ;
- campagne historique/backfill ;
- décodage STRUCTURAL/DECODED/DOMAIN. - 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 ## 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-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 ; - [`../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 ; - [`../../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 --> <!-- file: crates/ksp-worker-raw-transaction-ingest-lib/USAGE.md -->
<!-- version: 1 --> <!-- version: 2 -->
# Utilisation de ksp-worker-raw-transaction-ingest-lib # 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 ## 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::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), 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( return std::result::Result::Ok(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSettings::with_defaults(network, worker_id));
network,
worker_id,
));
} }
``` ```
Le Worker kind est fixe : Le Worker kind est fixe :
```rust ```rust
assert_eq!( assert_eq!(ksp_worker_raw_transaction_ingest_lib::RAW_TRANSACTION_INGEST_WORKER_KIND_CODE, "raw_transaction_ingest");
ksp_worker_raw_transaction_ingest_lib::RAW_TRANSACTION_INGEST_WORKER_KIND_CODE,
"raw_transaction_ingest",
);
``` ```
Pour des limites explicites : Pour des limites explicites :
@@ -57,22 +51,85 @@ 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. 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 ```rust
async fn start_worker( let handle = match ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestWorker::start(settings, store) {
settings: ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSettings, std::result::Result::Ok(value) => value,
store: std::sync::Arc<ksp_store_lib::Store>, std::result::Result::Err(error) => return std::result::Result::Err(error),
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHandle> { };
return ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestWorker::start(settings, store); ```
### 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 la source Yellowstone/HTTP cible ce même réseau.
## 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 Yellowstone sont traitées ainsi :
```text
Transaction -> signal -> HTTP getTransaction -> Common RAW -> admission
TransactionStatus -> signal -> HTTP getTransaction -> Common RAW -> admission
Block -> un signal par transaction -> HTTP getTransaction -> Common RAW -> admission
BlockMeta -> continuity-only
Slot -> continuity-only
Account/Ping/Pong/Entry -> sans RAW Transaction dans cette verticale
```
Les signaux de même `(network, signature, commitment)` sont coalescés avant l'hydration HTTP. Le Worker ne possède pas une boucle de retry HTTP : reroutage/retry/backoff restent dans `ksp-onchain-transport-lib`.
## Observer le snapshot concret ## Observer le snapshot concret
@@ -84,6 +141,10 @@ let sequence = current.worker_snapshot().sequence();
let state = current.worker_snapshot().state(); let state = current.worker_snapshot().state();
let queue_depth = current.admission_queue_depth(); let queue_depth = current.admission_queue_depth();
let in_flight = current.in_flight_persistence(); 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_state = current.source_state();
``` ```
Pour attendre une valeur plus récente : Pour attendre une valeur plus récente :
@@ -94,7 +155,7 @@ let newer = source.wait_for_change(observed).await;
assert!(newer.worker_snapshot().sequence().is_after(observed)); 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 ## Utiliser la projection Worker API
@@ -108,11 +169,9 @@ let newer = ksp_worker_api::WorkerSnapshotSource::wait_for_change(&source, obser
assert!(newer.sequence().is_after(observed)); 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 ## Lire les compteurs
Les compteurs principaux sont monotones :
```rust ```rust
let snapshot = handle.snapshot_source().current(); let snapshot = handle.snapshot_source().current();
@@ -128,11 +187,43 @@ let conflicts = snapshot.content_conflict_total();
let store_failures = snapshot.store_failure_total(); let store_failures = snapshot.store_failure_total();
let source_failures = snapshot.source_failure_total(); let source_failures = snapshot.source_failure_total();
let backpressure = snapshot.backpressure_wait_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();
``` ```
`admission_queue_depth()` et `in_flight_persistence()` sont des gauges latest-value, pas des totaux cumulés. `admission_queue_depth()`, `in_flight_persistence()` et `hydration_pending()` sont des gauges latest-value. Les compteurs cumulés ne wrapent jamais 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` après démarrage de la source productive.
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.
Si `source_continuity_gap_total` augmente, le Worker fault avec `worker_raw_transaction_ingest.source_failed`. Il ne déclenche pas automatiquement `ksp-job-backfill-lib`.
## Demander un stop et attendre le terminal ## Demander un stop et attendre le terminal
@@ -152,55 +243,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.
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 faults
## Interpréter les terminaux faultés
Les codes Worker publics sont :
```text ```text
settings_invalid settings techniques hors contrat settings_invalid settings techniques hors contrat
runtime_invalid invariant runtime/lifecycle impossible runtime_invalid invariant runtime/lifecycle impossible
store_failed erreur Store non-conflict classifiée 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 counter_exhausted compteur/séquence monotone arrivé à sa borne
source_failed tâche source privée terminée en erreur source_failed source/replay/hydration terminée par une erreur classifiée
drain_timeout drain du shutdown hors deadline 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. Un consumer doit traiter l'`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.
## Composition supérieure ## Composition supérieure
Le pattern de composition attendu reste : Le pattern attendu est :
```text ```text
Config / application / service owner Config / application / service owner
-> résout endpoints, credentials et rôles
-> construit YellowstoneGrpcChannel + YellowstoneSubscribeRequest
-> construit HttpTransportPool + hydration role
-> construit le Store -> construit le Store
-> choisit ultérieurement les sources/adapters supportés -> construit RawTransactionIngestRuntimeResources
-> construit RawTransactionIngestSettings -> construit RawTransactionIngestSettings
-> démarre RawTransactionIngestWorker -> démarre RawTransactionIngestWorker::start_with_runtime_resources
-> observe RawTransactionIngestSnapshotSource -> observe RawTransactionIngestSnapshotSource
-> demande stop lorsque nécessaire -> 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`.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs // file: crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
// version: 8 // version: 17
#![warn(missing_docs)] #![warn(missing_docs)]
#![deny(unreachable_pub)] #![deny(unreachable_pub)]
@@ -11,13 +11,19 @@
//! and the caller-runtime-owned lifecycle with private child-task supervision. This tranche also //! 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 //! 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; //! Store persistence in normal mode plus concrete latest-value snapshots projected onto Worker API;
//! no live source or Transport dependency exists. //! the validated Yellowstone/HTTP runtime-resource contract now drives one productive supervised source task.
//! Transaction/TransactionStatus/Block updates feed bounded HTTP `getTransaction` hydration and the existing central
//! admission path; 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 now also projects safe Transport reconnect/replay state and faults
//! conservatively when Transport proves a replay-retention continuity gap; history remediation remains outside this crate.
mod admission; mod admission;
mod error; mod error;
mod identity; mod identity;
mod persistence; mod persistence;
mod runtime; mod runtime;
mod runtime_resources;
mod settings; mod settings;
mod snapshot; mod snapshot;
@@ -43,6 +49,10 @@ pub use self::runtime::RawTransactionIngestHandle;
pub use self::runtime::RawTransactionIngestTerminalFuture; pub use self::runtime::RawTransactionIngestTerminalFuture;
/// Entry point owning synchronous validation and task launch for one RAW transaction ingest Worker run. /// Entry point owning synchronous validation and task launch for one RAW transaction ingest Worker run.
pub use self::runtime::RawTransactionIngestWorker; pub use self::runtime::RawTransactionIngestWorker;
/// Caller-composed runtime resources for the first Yellowstone + HTTP source family.
pub use self::runtime_resources::RawTransactionIngestRuntimeResources;
/// 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. /// Default bounded admission queue capacity for one RAW transaction ingest Worker.
pub use self::settings::DEFAULT_RAW_TRANSACTION_INGEST_ADMISSION_QUEUE_CAPACITY; pub use self::settings::DEFAULT_RAW_TRANSACTION_INGEST_ADMISSION_QUEUE_CAPACITY;
/// Default number of concurrent Store persistence operations for one RAW transaction ingest Worker. /// Default number of concurrent Store persistence operations for one RAW transaction ingest Worker.
@@ -69,6 +79,8 @@ pub use self::snapshot::RawTransactionIngestSnapshot;
pub use self::snapshot::RawTransactionIngestSnapshotFuture; pub use self::snapshot::RawTransactionIngestSnapshotFuture;
/// Cloneable latest-value source exposing concrete and common Worker snapshots from one shared watch state. /// Cloneable latest-value source exposing concrete and common Worker snapshots from one shared watch state.
pub use self::snapshot::RawTransactionIngestSnapshotSource; 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. /// Receiver-side owner of the private bounded RAW transaction admission queue.
pub(crate) use self::admission::RawTransactionAdmission; pub(crate) use self::admission::RawTransactionAdmission;
@@ -94,5 +106,7 @@ pub(crate) use self::persistence::RawTransactionIngestPersistenceOutcome;
pub(crate) use self::persistence::RawTransactionIngestPersistencePort; pub(crate) use self::persistence::RawTransactionIngestPersistencePort;
/// Persists one already-canonical Worker acquisition through the private Store port in `Normal` mode. /// Persists one already-canonical Worker acquisition through the private Store port in `Normal` mode.
pub(crate) use self::persistence::persist_raw_transaction_ingest_acquisition; pub(crate) use self::persistence::persist_raw_transaction_ingest_acquisition;
/// 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. /// Private latest-value publisher and checked counter owner shared by the Worker supervisor.
pub(crate) use self::snapshot::RawTransactionIngestSnapshotPublisher; pub(crate) use self::snapshot::RawTransactionIngestSnapshotPublisher;

View File

@@ -1,8 +1,9 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/runtime.rs // file: crates/ksp-worker-raw-transaction-ingest-lib/src/runtime.rs
// version: 7 // version: 11
type PersistencePort = std::sync::Arc<dyn crate::RawTransactionIngestPersistencePort + 'static>; type PersistencePort = std::sync::Arc<dyn crate::RawTransactionIngestPersistencePort + 'static>;
type PersistenceTasks = tokio::task::JoinSet<ksp_core_lib::Result<crate::RawTransactionIngestPersistenceOutcome>>; 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<()>>; 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. /// Runtime-neutral boxed future resolving after one RAW transaction ingest Worker has fully reached a terminal lifecycle state.
@@ -95,6 +96,41 @@ impl crate::RawTransactionIngestWorker {
} }
return start_foundation(settings, runtime, std::option::Option::Some(store)); return start_foundation(settings, runtime, std::option::Option::Some(store));
} }
/// Starts one Worker with caller-composed runtime resources and one supervised productive Yellowstone source task.
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 = runtime_resources.into_yellowstone_source();
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),
move |children, stop_receiver, admission_sender| {
let _abort_handle = children.spawn(async move {
return source.run(source_settings, stop_receiver, admission_sender, processing_frontier_sender).await;
});
},
);
}
} }
fn begin_stopping( fn begin_stopping(
@@ -318,6 +354,7 @@ async fn run_supervisor<Spawner>(
port: std::option::Option<PersistencePort>, port: std::option::Option<PersistencePort>,
mut stop_receiver: tokio::sync::watch::Receiver<bool>, mut stop_receiver: tokio::sync::watch::Receiver<bool>,
mut snapshots: crate::RawTransactionIngestSnapshotPublisher, mut snapshots: crate::RawTransactionIngestSnapshotPublisher,
mut processing_frontier_receiver: std::option::Option<ProcessingFrontierReceiver>,
source_spawner: Spawner, source_spawner: Spawner,
) where ) where
Spawner: Spawner:
@@ -354,6 +391,7 @@ async fn run_supervisor<Spawner>(
&mut persistence, &mut persistence,
&port, &port,
&mut snapshots, &mut snapshots,
&mut processing_frontier_receiver,
) )
.await; .await;
source_stop_sender.send_replace(true); source_stop_sender.send_replace(true);
@@ -405,6 +443,20 @@ fn start_foundation_with_port_and_source_spawner<Spawner>(
port: std::option::Option<PersistencePort>, port: std::option::Option<PersistencePort>,
source_spawner: Spawner, source_spawner: Spawner,
) -> ksp_core_lib::Result<crate::RawTransactionIngestHandle> ) -> 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, 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_spawner: Spawner,
) -> ksp_core_lib::Result<crate::RawTransactionIngestHandle>
where where
Spawner: Spawner:
FnOnce(&mut SourceTasks, tokio::sync::watch::Receiver<bool>, tokio::sync::mpsc::Sender<crate::RawTransactionIngress>) + std::marker::Send + 'static, FnOnce(&mut SourceTasks, tokio::sync::watch::Receiver<bool>, tokio::sync::mpsc::Sender<crate::RawTransactionIngress>) + std::marker::Send + 'static,
@@ -421,7 +473,7 @@ where
let (stop_sender, stop_receiver) = tokio::sync::watch::channel(false); 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);
let handle = crate::RawTransactionIngestHandle { snapshots: snapshot_source, stop_sender, stop_token }; 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); return std::result::Result::Ok(handle);
} }
@@ -455,6 +507,7 @@ async fn supervise_until_stop(
persistence: &mut PersistenceTasks, persistence: &mut PersistenceTasks,
port: &std::option::Option<PersistencePort>, port: &std::option::Option<PersistencePort>,
snapshots: &mut crate::RawTransactionIngestSnapshotPublisher, snapshots: &mut crate::RawTransactionIngestSnapshotPublisher,
processing_frontier_receiver: &mut std::option::Option<ProcessingFrontierReceiver>,
) -> std::option::Option<ksp_core_lib::ErrorCode> { ) -> std::option::Option<ksp_core_lib::ErrorCode> {
let mut admission_open = true; let mut admission_open = true;
loop { loop {
@@ -478,6 +531,25 @@ async fn supervise_until_stop(
break; 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() => { joined = children.join_next(), if !children.is_empty() => {
let source_fault = match joined { let source_fault = match joined {
std::option::Option::Some(value) => source_completion(value, lifecycle.state(), admission.queue_depth(), persistence.len(), snapshots), std::option::Option::Some(value) => source_completion(value, lifecycle.state(), admission.queue_depth(), persistence.len(), snapshots),
@@ -545,6 +617,21 @@ async fn supervise_until_stop(
return std::option::Option::None; 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());
}
fn validate_store_network(settings: &crate::RawTransactionIngestSettings, store_network: &ksp_store_lib::RawNetworkId) -> ksp_core_lib::Result<()> { fn validate_store_network(settings: &crate::RawTransactionIngestSettings, store_network: &ksp_store_lib::RawNetworkId) -> ksp_core_lib::Result<()> {
if settings.network() != store_network { if settings.network() != store_network {
return std::result::Result::Err(crate::runtime_error("start.store_network_mismatch")); 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,10 +1,119 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/snapshot.rs // file: crates/ksp-worker-raw-transaction-ingest-lib/src/snapshot.rs
// version: 2 // version: 4
/// Runtime-neutral boxed future resolving to one newer concrete RAW transaction ingest Worker snapshot. /// Runtime-neutral boxed future resolving to one newer concrete RAW transaction ingest Worker snapshot.
pub type RawTransactionIngestSnapshotFuture<'a> = pub type RawTransactionIngestSnapshotFuture<'a> =
std::pin::Pin<std::boxed::Box<dyn std::future::Future<Output = crate::RawTransactionIngestSnapshot> + std::marker::Send + '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,
}
/// Private latest-value source-processing projection emitted by the productive source task.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct RawTransactionIngestProcessingFrontierProjection {
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_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) const fn empty() -> Self {
return Self {
hydration_pending: 0,
processing_frontier_slot: std::option::Option::None,
oldest_pending_slot: std::option::Option::None,
source_state: std::option::Option::None,
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) const fn new(
hydration_pending: usize,
processing_frontier_slot: std::option::Option<u64>,
oldest_pending_slot: std::option::Option<u64>,
) -> Self {
return Self {
hydration_pending,
processing_frontier_slot,
oldest_pending_slot,
source_state: std::option::Option::None,
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) const 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 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. /// Complete safe latest-value snapshot of one continuous RAW transaction ingest Worker.
#[derive(Clone, Eq, PartialEq)] #[derive(Clone, Eq, PartialEq)]
pub struct RawTransactionIngestSnapshot { pub struct RawTransactionIngestSnapshot {
@@ -25,6 +134,13 @@ pub struct RawTransactionIngestSnapshot {
store_failure_total: u64, store_failure_total: u64,
source_failure_total: u64, source_failure_total: u64,
backpressure_wait_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_reconnect_total: u64,
source_replay_attempt_total: u64,
source_continuity_gap_total: u64,
} }
impl crate::RawTransactionIngestSnapshot { impl crate::RawTransactionIngestSnapshot {
@@ -129,6 +245,48 @@ impl crate::RawTransactionIngestSnapshot {
pub const fn backpressure_wait_total(&self) -> u64 { pub const fn backpressure_wait_total(&self) -> u64 {
return self.backpressure_wait_total; 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 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 { impl std::fmt::Debug for crate::RawTransactionIngestSnapshot {
@@ -152,6 +310,13 @@ impl std::fmt::Debug for crate::RawTransactionIngestSnapshot {
.field("store_failure_total", &self.store_failure_total) .field("store_failure_total", &self.store_failure_total)
.field("source_failure_total", &self.source_failure_total) .field("source_failure_total", &self.source_failure_total)
.field("backpressure_wait_total", &self.backpressure_wait_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_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(); .finish();
} }
} }
@@ -265,6 +430,13 @@ impl crate::RawTransactionIngestSnapshotPublisher {
store_failure_total: 0, store_failure_total: 0,
source_failure_total: 0, source_failure_total: 0,
backpressure_wait_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_reconnect_total: 0,
source_replay_attempt_total: 0,
source_continuity_gap_total: 0,
}; };
let (sender, receiver) = tokio::sync::watch::channel(snapshot.clone()); let (sender, receiver) = tokio::sync::watch::channel(snapshot.clone());
return (Self { sender, snapshot }, crate::RawTransactionIngestSnapshotSource { receiver }); return (Self { sender, snapshot }, crate::RawTransactionIngestSnapshotSource { receiver });
@@ -346,6 +518,24 @@ impl crate::RawTransactionIngestSnapshotPublisher {
return self.publish(state, admission_queue_depth, in_flight_persistence); 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.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();
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. /// Records one failed private source task without retaining provider-specific error material.
pub(crate) fn record_source_failure( pub(crate) fn record_source_failure(
&mut self, &mut self,
@@ -457,7 +647,7 @@ impl crate::RawTransactionIngestSnapshotPublisher {
sequence, sequence,
state, state,
health_for_state(state, self.snapshot.worker.health()), health_for_state(state, self.snapshot.worker.health()),
activity_for_state(state, admission_queue_depth, in_flight_persistence), activity_for_state(state, admission_queue_depth, in_flight_persistence, self.snapshot.hydration_pending, self.snapshot.source_state),
); );
self.snapshot.worker = worker; self.snapshot.worker = worker;
self.snapshot.admission_queue_depth = admission_queue_depth; self.snapshot.admission_queue_depth = admission_queue_depth;
@@ -467,8 +657,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 { fn activity_for_state(
if admission_queue_depth > 0 || in_flight_persistence > 0 { 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 ksp_worker_api::WorkerActivity::Active;
} }
return match state { return match state {

View File

@@ -0,0 +1,192 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/tests/cross_layer_completeness.rs
// version: 1
//! Cross-layer completeness and security canaries for `0.3.12-pre.010`.
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;
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs // file: crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
// version: 8 // version: 16
//! Dependency firewall canaries for the RAW transaction ingest Worker foundation. //! 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 manifest = include_str!("../Cargo.toml");
let dependencies = dependency_section(manifest); let dependencies = dependency_section(manifest);
let names = manifest_dependency_names(dependencies); 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 [ for required in [
"ksp-core-lib = { path = \"../ksp-core-lib\" }", "ksp-core-lib = { path = \"../ksp-core-lib\" }",
"ksp-logging-lib = { path = \"../ksp-logging-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-raw-transaction-lib = { path = \"../ksp-raw-transaction-lib\" }",
"ksp-store-lib = { path = \"../ksp-store-lib\", default-features = false }", "ksp-store-lib = { path = \"../ksp-store-lib\", default-features = false }",
"ksp-worker-api = { path = \"../ksp-worker-api\" }", "ksp-worker-api = { path = \"../ksp-worker-api\" }",
@@ -24,7 +37,7 @@ fn pre_002_manifest_dependency_surface_is_exact() {
} }
#[test] #[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 manifest = include_str!("../Cargo.toml");
let dependencies = dependency_section(manifest); let dependencies = dependency_section(manifest);
for forbidden in [ for forbidden in [
@@ -33,7 +46,6 @@ fn pre_002_manifest_keeps_forbidden_layers_and_live_sources_out() {
"ksp-interface-lib", "ksp-interface-lib",
"ksp-job-api", "ksp-job-api",
"ksp-job-backfill-lib", "ksp-job-backfill-lib",
"ksp-onchain-transport-lib",
"ksp-program-api", "ksp-program-api",
"ksp-store-api", "ksp-store-api",
"ksp-store-postgres-lib", "ksp-store-postgres-lib",
@@ -52,12 +64,164 @@ fn pre_002_manifest_keeps_forbidden_layers_and_live_sources_out() {
} }
#[test] #[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",
"get_block_observed",
"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_yellowstone_hydration"));
assert!(resources.contains("fn fetch_yellowstone_hydration"));
for forbidden in [
"get_block_observed",
"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 [
"get_block_observed",
"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 [
"runtime_resources.into_yellowstone_source()",
"children.spawn",
"source.run(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",
"MAX_RAW_TRANSACTION_INGEST_ADMISSION_QUEUE_CAPACITY",
"get_transaction_observed",
"admission_sender.send(ingress)",
"tasks.abort_all()",
"session.close().await",
"commitment: 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 [
"get_block_observed",
"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_12_pre_006_source_surface_hardens_shutdown_and_faults_without_backend_edges() {
let root = include_str!("../src/lib.rs"); let root = include_str!("../src/lib.rs");
let runtime = include_str!("../src/runtime.rs"); let runtime = include_str!("../src/runtime.rs");
let admission = include_str!("../src/admission.rs"); let admission = include_str!("../src/admission.rs");
let persistence = include_str!("../src/persistence.rs"); let persistence = include_str!("../src/persistence.rs");
let snapshot = include_str!("../src/snapshot.rs"); let snapshot = include_str!("../src/snapshot.rs");
let runtime_resources = include_str!("../src/runtime_resources.rs");
for required in [ for required in [
"tokio::sync::mpsc::channel", "tokio::sync::mpsc::channel",
"canonicalize_raw_transaction", "canonicalize_raw_transaction",
@@ -81,7 +245,8 @@ fn pre_009_source_surface_hardens_shutdown_and_faults_without_backend_or_live_so
|| runtime.contains(required) || runtime.contains(required)
|| admission.contains(required) || admission.contains(required)
|| persistence.contains(required) || persistence.contains(required)
|| snapshot.contains(required), || snapshot.contains(required)
|| runtime_resources.contains(required),
"required pre.009 hardening contract missing: {required}" "required pre.009 hardening contract missing: {required}"
); );
} }
@@ -91,7 +256,6 @@ fn pre_009_source_surface_hardens_shutdown_and_faults_without_backend_or_live_so
"pub use self::persistence::RawTransactionIngestPersistencePort", "pub use self::persistence::RawTransactionIngestPersistencePort",
"unbounded_channel", "unbounded_channel",
"ksp_store_postgres_lib::", "ksp_store_postgres_lib::",
"ksp_onchain_transport_lib::",
"ForceRehydrate", "ForceRehydrate",
"ksp_config_lib::", "ksp_config_lib::",
"reqwest::", "reqwest::",
@@ -101,11 +265,22 @@ fn pre_009_source_surface_hardens_shutdown_and_faults_without_backend_or_live_so
&& !runtime.contains(forbidden) && !runtime.contains(forbidden)
&& !admission.contains(forbidden) && !admission.contains(forbidden)
&& !persistence.contains(forbidden) && !persistence.contains(forbidden)
&& !snapshot.contains(forbidden), && !snapshot.contains(forbidden)
&& !runtime_resources.contains(forbidden),
"pre.009 crossed a forbidden runtime boundary: {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"); assert_eq!(snapshot.matches("tokio::sync::watch::channel").count(), 1, "exactly one shared concrete snapshot watch is allowed");
return; return;
} }
@@ -145,3 +320,68 @@ fn manifest_dependency_names(section: &str) -> std::vec::Vec<&str> {
names.sort_unstable(); names.sort_unstable();
return names; return names;
} }
#[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;
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs // file: crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
// version: 2 // version: 11
//! External public, security, redaction and release-boundary hardening canaries for `pre.010`. //! External public, security, redaction and release-boundary hardening canaries for `pre.010`.
@@ -92,7 +92,7 @@ fn pre_010_debug_and_settings_errors_redact_worker_identity_and_invalid_values()
} }
#[test] #[test]
fn pre_010_manifest_dependency_surface_remains_exact_source_neutral_and_backend_neutral() { fn v0_3_12_pre_002_manifest_dependency_surface_opens_only_transport_and_remains_backend_neutral() {
let manifest = include_str!("../Cargo.toml"); let manifest = include_str!("../Cargo.toml");
let mut section = ""; let mut section = "";
let mut normal = std::collections::BTreeSet::new(); let mut normal = std::collections::BTreeSet::new();
@@ -121,7 +121,16 @@ fn pre_010_manifest_dependency_surface_remains_exact_source_neutral_and_backend_
} }
assert_eq!( assert_eq!(
normal, normal,
std::collections::BTreeSet::from(["ksp-core-lib", "ksp-logging-lib", "ksp-raw-transaction-lib", "ksp-store-lib", "ksp-worker-api", "sha2", "tokio",]) std::collections::BTreeSet::from([
"ksp-core-lib",
"ksp-logging-lib",
"ksp-onchain-transport-lib",
"ksp-raw-transaction-lib",
"ksp-store-lib",
"ksp-worker-api",
"sha2",
"tokio",
])
); );
assert!(dev.is_empty()); assert!(dev.is_empty());
assert!(build.is_empty()); assert!(build.is_empty());
@@ -131,7 +140,6 @@ fn pre_010_manifest_dependency_surface_remains_exact_source_neutral_and_backend_
"ksp-config-lib", "ksp-config-lib",
"ksp-job-api", "ksp-job-api",
"ksp-job-backfill-lib", "ksp-job-backfill-lib",
"ksp-onchain-transport-lib",
"ksp-store-api", "ksp-store-api",
"ksp-store-postgres-lib", "ksp-store-postgres-lib",
"reqwest", "reqwest",
@@ -146,11 +154,12 @@ fn pre_010_manifest_dependency_surface_remains_exact_source_neutral_and_backend_
#[test] #[test]
fn pre_010_source_visibility_contract_uses_crate_root_for_shared_items() { fn pre_010_source_visibility_contract_uses_crate_root_for_shared_items() {
let source_contracts: [(&str, &[&str]); 4] = [ let source_contracts: [(&str, &[&str]); 5] = [
(include_str!("../src/settings.rs"), &["RawTransactionIngestSettings"]), (include_str!("../src/settings.rs"), &["RawTransactionIngestSettings"]),
(include_str!("../src/runtime.rs"), &["RawTransactionIngestHandle", "RawTransactionIngestWorker"]), (include_str!("../src/runtime.rs"), &["RawTransactionIngestHandle", "RawTransactionIngestWorker"]),
(include_str!("../src/snapshot.rs"), &["RawTransactionIngestSnapshot", "RawTransactionIngestSnapshotSource"]), (include_str!("../src/snapshot.rs"), &["RawTransactionIngestSnapshot", "RawTransactionIngestSnapshotSource"]),
(include_str!("../src/persistence.rs"), &["RawTransactionIngestPersistenceOutcome", "RawTransactionIngestPersistencePort"]), (include_str!("../src/persistence.rs"), &["RawTransactionIngestPersistenceOutcome", "RawTransactionIngestPersistencePort"]),
(include_str!("../src/runtime_resources.rs"), &["RawTransactionIngestYellowstoneSource", "RawTransactionIngestRuntimeResources"]),
]; ];
for (source, symbols) in source_contracts { for (source, symbols) in source_contracts {
for symbol in symbols { for symbol in symbols {
@@ -166,6 +175,7 @@ fn pre_010_source_visibility_contract_uses_crate_root_for_shared_items() {
("identity", include_str!("../src/identity.rs")), ("identity", include_str!("../src/identity.rs")),
("persistence", include_str!("../src/persistence.rs")), ("persistence", include_str!("../src/persistence.rs")),
("runtime", include_str!("../src/runtime.rs")), ("runtime", include_str!("../src/runtime.rs")),
("runtime_resources", include_str!("../src/runtime_resources.rs")),
("settings", include_str!("../src/settings.rs")), ("settings", include_str!("../src/settings.rs")),
("snapshot", include_str!("../src/snapshot.rs")), ("snapshot", include_str!("../src/snapshot.rs")),
] { ] {
@@ -184,6 +194,7 @@ fn pre_010_production_surface_has_no_historical_backfill_or_retriever_contract()
include_str!("../src/lib.rs"), include_str!("../src/lib.rs"),
include_str!("../src/persistence.rs"), include_str!("../src/persistence.rs"),
include_str!("../src/runtime.rs"), include_str!("../src/runtime.rs"),
include_str!("../src/runtime_resources.rs"),
include_str!("../src/settings.rs"), include_str!("../src/settings.rs"),
include_str!("../src/snapshot.rs"), include_str!("../src/snapshot.rs"),
]; ];
@@ -198,8 +209,6 @@ fn pre_010_production_surface_has_no_historical_backfill_or_retriever_contract()
"checkpoint", "checkpoint",
"Discovery", "Discovery",
"discovery", "discovery",
"Hydration",
"hydration",
"historical", "historical",
] { ] {
assert!(!source.contains(forbidden), "historical/retriever surface leaked into Worker production source: {forbidden}"); assert!(!source.contains(forbidden), "historical/retriever surface leaked into Worker production source: {forbidden}");
@@ -209,8 +218,39 @@ fn pre_010_production_surface_has_no_historical_backfill_or_retriever_contract()
} }
#[test] #[test]
fn pre_010_production_sources_scan_clean_for_config_secrets_backend_and_transport() { fn v0_3_12_pre_002_production_sources_keep_transport_confined_to_runtime_resources() {
let sources = [ for source in [
include_str!("../src/admission.rs"),
include_str!("../src/error.rs"),
include_str!("../src/identity.rs"),
include_str!("../src/lib.rs"),
include_str!("../src/persistence.rs"),
include_str!("../src/runtime.rs"),
include_str!("../src/runtime_resources.rs"),
include_str!("../src/settings.rs"),
include_str!("../src/snapshot.rs"),
] {
let lower = source.to_ascii_lowercase();
for forbidden in ["api_key", "api-key", "authorization", "bearer ", "password", "credential", "secret"] {
assert!(!lower.contains(forbidden), "secret-like material leaked into Worker production source: {forbidden}");
}
}
let transport_source = include_str!("../src/runtime_resources.rs");
assert!(transport_source.contains("ksp_onchain_transport_lib::"));
for forbidden in [
"ksp_config_lib::",
"ksp_store_postgres_lib::",
"ksp_offchain_transport_lib::",
"reqwest::",
"tokio_tungstenite::",
"tonic::",
"yellowstone_grpc_proto::",
"postgresql://",
"postgres://",
] {
assert!(!transport_source.contains(forbidden), "forbidden implementation detail leaked into runtime resources: {forbidden}");
}
for source in [
include_str!("../src/admission.rs"), include_str!("../src/admission.rs"),
include_str!("../src/error.rs"), include_str!("../src/error.rs"),
include_str!("../src/identity.rs"), include_str!("../src/identity.rs"),
@@ -219,16 +259,11 @@ fn pre_010_production_sources_scan_clean_for_config_secrets_backend_and_transpor
include_str!("../src/runtime.rs"), include_str!("../src/runtime.rs"),
include_str!("../src/settings.rs"), include_str!("../src/settings.rs"),
include_str!("../src/snapshot.rs"), include_str!("../src/snapshot.rs"),
]; ] {
for source in sources { assert!(!source.contains("ksp_onchain_transport_lib::"), "Transport dependency escaped runtime_resources.rs");
let lower = source.to_ascii_lowercase();
for forbidden in ["api_key", "api-key", "authorization", "bearer ", "password", "credential", "secret"] {
assert!(!lower.contains(forbidden), "secret-like material leaked into Worker production source: {forbidden}");
}
for forbidden in [ for forbidden in [
"ksp_config_lib::", "ksp_config_lib::",
"ksp_store_postgres_lib::", "ksp_store_postgres_lib::",
"ksp_onchain_transport_lib::",
"ksp_offchain_transport_lib::", "ksp_offchain_transport_lib::",
"reqwest::", "reqwest::",
"tokio_tungstenite::", "tokio_tungstenite::",
@@ -237,12 +272,153 @@ fn pre_010_production_sources_scan_clean_for_config_secrets_backend_and_transpor
"postgresql://", "postgresql://",
"postgres://", "postgres://",
] { ] {
assert!(!source.contains(forbidden), "Config/backend/Transport implementation leaked into Worker production source: {forbidden}"); assert!(!source.contains(forbidden), "forbidden implementation detail leaked into Worker production source: {forbidden}");
} }
} }
return; return;
} }
#[test]
fn v0_3_12_pre_003_private_signal_debug_and_shape_do_not_expose_signature_filters_or_payload() {
let root = include_str!("../src/lib.rs");
let resources = include_str!("../src/runtime_resources.rs");
assert!(resources.contains("struct RawTransactionIngestSourceSignal"));
assert!(!root.contains("RawTransactionIngestSourceSignal"));
assert!(!resources.contains(".field(\"matched_filter_fingerprint\", &self.matched_filter_fingerprint)"));
assert!(!resources.contains(".field(\"signature\", &self.signature)"));
let signal_struct = match resources.split_once("struct RawTransactionIngestSourceSignal {") {
std::option::Option::Some((_, tail)) => match tail.split_once("impl std::fmt::Debug for RawTransactionIngestSourceSignal") {
std::option::Option::Some((value, _)) => value,
std::option::Option::None => "",
},
std::option::Option::None => "",
};
for forbidden in ["transaction:", "meta:", "error:", "payload:", "body:", "is_vote:"] {
assert!(!signal_struct.contains(forbidden), "payload/provider-specific field leaked into private source signal: {forbidden}");
}
for required in [
"created_at:",
"family:",
"matched_filter_count:",
"matched_filter_fingerprint:",
"matched_filter_id:",
"network:",
"route:",
"signature:",
"slot:",
"transaction_index:",
] {
assert!(signal_struct.contains(required), "required private signal field missing: {required}");
}
return;
}
#[test]
fn v0_3_12_pre_004_hydration_provenance_and_remote_material_are_bounded_and_redacted() {
let resources = include_str!("../src/runtime_resources.rs");
for required in [
"ys.{}:http.{}",
"composite_provider_unrepresentable",
"composite_endpoint_unrepresentable",
"RawAcquisitionOrigin::Live",
"with_capture_session_id",
"with_commitment",
"with_endpoint_id",
"with_filter_id",
"try_with_observed_at",
"hydration.signature_mismatch",
"hydration.slot_mismatch",
"hydration.transaction_index_mismatch",
] {
assert!(resources.contains(required), "required pre.004 bounded provenance/mismatch guard missing: {required}");
}
for forbidden in ["source_payload_hash", "source_payload_size_bytes", "HTTP-SECRET-CANARY", "GRPC-SECRET-CANARY", "TransactionStatus.error", ".error()"] {
assert!(!resources.contains(forbidden), "pre.004 retained forbidden remote/source material: {forbidden}");
}
return;
}
#[test]
fn v0_3_12_pre_005_block_and_continuity_shapes_are_bounded_redacted_and_raw_separated() {
let resources = include_str!("../src/runtime_resources.rs");
for required in [
"impl RawTransactionIngestYellowstoneBlockView for ksp_onchain_transport_lib::YellowstoneBlockUpdate",
"impl RawTransactionIngestYellowstoneContinuityView for ksp_onchain_transport_lib::YellowstoneBlockMetaUpdate",
"impl RawTransactionIngestYellowstoneContinuityView for ksp_onchain_transport_lib::YellowstoneSlotUpdate",
"RawTransactionIngestSourceFamily::Block",
"project_yellowstone_block_signals",
"project_yellowstone_continuity_signal",
"RawTransactionIngestContinuityStatus::Dead",
"block_get_transaction",
] {
assert!(resources.contains(required), "required pre.005 bounded adapter guard missing: {required}");
}
let continuity_struct = match resources.split_once("struct RawTransactionIngestContinuitySignal {") {
std::option::Option::Some((_, tail)) => match tail.split_once("impl std::fmt::Debug for RawTransactionIngestContinuitySignal") {
std::option::Option::Some((value, _)) => value,
std::option::Option::None => "",
},
std::option::Option::None => "",
};
for required in [
"created_at:",
"family:",
"matched_filter_count:",
"matched_filter_fingerprint:",
"matched_filter_id:",
"network:",
"parent_slot:",
"route:",
"slot:",
"status:",
] {
assert!(continuity_struct.contains(required), "required continuity-only field missing: {required}");
}
for forbidden in ["signature:", "transaction:", "meta:", "payload:", "body:", "error:", "dead_error:"] {
assert!(!continuity_struct.contains(forbidden), "RAW/remote material leaked into continuity-only signal: {forbidden}");
}
assert!(!resources.contains("YellowstoneSlotUpdate::dead_error"));
return;
}
#[test]
fn v0_3_12_pre_006_runtime_resource_contract_opens_one_supervised_transport_source() {
let resources = include_str!("../src/runtime_resources.rs");
let runtime = include_str!("../src/runtime.rs");
for required in ["open_standard_subscribe", "next_update", "get_transaction_observed", "RawTransactionIngestHydrationCoordinator", "session.close().await"]
{
assert!(resources.contains(required), "productive runtime-resource source behavior missing: {required}");
}
assert!(runtime.contains("start_with_runtime_resources"));
assert!(runtime.contains("source.run(source_settings, stop_receiver, admission_sender, processing_frontier_sender)"));
for forbidden in ["get_block_observed", "ksp_config_lib::", "ksp_store_postgres_lib::", "reqwest::", "tonic::", "yellowstone_grpc_proto::"] {
assert!(!resources.contains(forbidden) && !runtime.contains(forbidden), "pre.006 runtime source crossed a forbidden boundary: {forbidden}");
}
return;
}
#[test]
fn v0_3_12_pre_006_source_coalescence_is_bounded_stop_preemptible_and_redacted() {
let resources = include_str!("../src/runtime_resources.rs");
for required in [
"max_in_flight: settings.persistence_concurrency()",
"max_pending_signals: crate::MAX_RAW_TRANSACTION_INGEST_ADMISSION_QUEUE_CAPACITY",
"pending_signal_count",
"stop_receiver.changed()",
"tasks.abort_all()",
"source.hydration_pending_saturated",
"source.hydration_task_join_failed",
"hydration_transport_error(error.code())",
"source_transport_error(error.code())",
] {
assert!(resources.contains(required), "required pre.006 bounded/stop-safe source guard missing: {required}");
}
for forbidden in ["remote_message", "response_body", "dead_error", "TransactionStatus.error", ".error()", "Authorization", "Bearer "] {
assert!(!resources.contains(forbidden), "remote/secret material leaked into productive source: {forbidden}");
}
return;
}
#[test] #[test]
fn pre_010_lower_layers_have_no_dependency_return_to_concrete_worker() { fn pre_010_lower_layers_have_no_dependency_return_to_concrete_worker() {
for manifest in [ for manifest in [
@@ -260,7 +436,7 @@ fn pre_010_lower_layers_have_no_dependency_return_to_concrete_worker() {
} }
#[test] #[test]
fn pre_010_public_root_exposes_no_runtime_backend_or_live_source_implementation_types() { fn v0_3_12_pre_002_public_root_exposes_contract_types_without_transport_implementation_paths() {
let root = include_str!("../src/lib.rs"); let root = include_str!("../src/lib.rs");
for forbidden in [ for forbidden in [
"pub mod ", "pub mod ",
@@ -279,3 +455,89 @@ fn pre_010_public_root_exposes_no_runtime_backend_or_live_source_implementation_
} }
return; return;
} }
#[test]
fn v0_3_12_pre_007_processing_frontier_is_bounded_processing_only_and_redacted() {
let resources = include_str!("../src/runtime_resources.rs");
let snapshot = include_str!("../src/snapshot.rs");
for required in [
"pending_total",
"std::collections::BTreeMap<u64, RawTransactionIngestProcessingSlotState>",
"settled_interval_highs",
"oldest_pending_slot",
"processing_frontier_slot",
"hydration_pending",
"processing_frontier.observe_pending",
"processing_frontier.settle_pending",
] {
assert!(resources.contains(required) || snapshot.contains(required), "required pre.007 bounded frontier guard 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 [
"signature: ksp_store_lib::RawTransactionSignature",
"filter_id",
"provider",
"endpoint_id",
"transaction:",
"meta:",
"ReplayInfo",
"from_slot",
"continuity_gap",
"repair",
] {
assert!(!frontier.contains(forbidden), "pre.007 frontier absorbed forbidden material: {forbidden}");
}
return;
}
#[test]
fn v0_3_12_pre_008_reconnect_projection_is_source_neutral_bounded_and_contains_no_replay_material() {
let resources = include_str!("../src/runtime_resources.rs");
let snapshot = include_str!("../src/snapshot.rs");
for required in [
"RawTransactionIngestSourceState",
"source_reconnect_total",
"source_replay_attempt_total",
"source_continuity_gap_total",
"source.continuity_counter_regression",
"source.continuity_gap_proven",
] {
assert!(resources.contains(required) || snapshot.contains(required), "required pre.008 bounded continuity guard missing: {required}");
}
let projection = match snapshot.split_once("struct RawTransactionIngestProcessingFrontierProjection {") {
std::option::Option::Some((_, tail)) => match tail.split_once("impl crate::RawTransactionIngestProcessingFrontierProjection") {
std::option::Option::Some((value, _)) => value,
std::option::Option::None => "",
},
std::option::Option::None => "",
};
for forbidden in ["signature", "provider", "endpoint", "filter", "transaction", "meta", "from_slot", "first_available", "ReplayInfo"] {
assert!(!projection.contains(forbidden), "pre.008 source projection leaked replay/provider/RAW material: {forbidden}");
}
return;
}
#[test]
fn v0_3_12_pre_009_hydration_retry_ownership_and_no_orphan_cleanup_are_explicit() {
let resources = include_str!("../src/runtime_resources.rs");
assert_eq!(resources.matches("get_transaction_observed(").count(), 1, "Worker must delegate one hydration attempt to Transport");
for required in [
"coordinator.abort_all(&mut processing_frontier).await",
"fn discard_all_pending(&mut self)",
"processing_frontier.discard_all_pending()",
"source.hydration_pending_saturated",
"tasks.abort_all()",
] {
assert!(resources.contains(required), "required pre.009 no-orphan/backpressure guard missing: {required}");
}
for forbidden in ["tokio::time::sleep", "tokio::time::interval", "get_block_observed", "unbounded_channel"] {
assert!(!resources.contains(forbidden), "Worker introduced forbidden retry/unbounded behavior: {forbidden}");
}
return;
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/tests/public_api.rs // file: crates/ksp-worker-raw-transaction-ingest-lib/tests/public_api.rs
// version: 8 // version: 11
//! External public-surface proofs for the RAW transaction ingest Worker foundation. //! 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; 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>() {} 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>(); 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.domain(), "worker_raw_transaction_ingest");
assert_eq!(ksp_worker_raw_transaction_ingest_lib::ERROR_CODE_RAW_TRANSACTION_INGEST_COUNTER_EXHAUSTED.code(), "counter_exhausted"); assert_eq!(ksp_worker_raw_transaction_ingest_lib::ERROR_CODE_RAW_TRANSACTION_INGEST_COUNTER_EXHAUSTED.code(), "counter_exhausted");
return; return;
@@ -135,3 +147,52 @@ 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"); assert_eq!(ksp_worker_raw_transaction_ingest_lib::ERROR_CODE_RAW_TRANSACTION_INGEST_SOURCE_FAILED.code(), "source_failed");
return; 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_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;
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs // file: crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
// version: 1 // version: 9
//! Release-completeness canaries through the `pre.010` public/release/security hardening tranche. //! Release-completeness canaries through the `pre.010` public/release/security hardening tranche.
@@ -32,7 +32,10 @@ fn pre_010_production_module_inventory_is_exact() -> std::io::Result<()> {
} }
} }
names.sort_unstable(); 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", "error.rs", "identity.rs", "lib.rs", "persistence.rs", "runtime.rs", "runtime_resources.rs", "settings.rs", "snapshot.rs",]
);
return std::result::Result::Ok(()); return std::result::Result::Ok(());
} }
@@ -74,12 +77,15 @@ fn pre_010_public_root_export_inventory_is_exact() {
"MIN_RAW_TRANSACTION_INGEST_SHUTDOWN_DRAIN_TIMEOUT", "MIN_RAW_TRANSACTION_INGEST_SHUTDOWN_DRAIN_TIMEOUT",
"RAW_TRANSACTION_INGEST_WORKER_KIND_CODE", "RAW_TRANSACTION_INGEST_WORKER_KIND_CODE",
"RawTransactionIngestHandle", "RawTransactionIngestHandle",
"RawTransactionIngestRuntimeResources",
"RawTransactionIngestSettings", "RawTransactionIngestSettings",
"RawTransactionIngestSnapshot", "RawTransactionIngestSnapshot",
"RawTransactionIngestSnapshotFuture", "RawTransactionIngestSnapshotFuture",
"RawTransactionIngestSnapshotSource", "RawTransactionIngestSnapshotSource",
"RawTransactionIngestSourceState",
"RawTransactionIngestTerminalFuture", "RawTransactionIngestTerminalFuture",
"RawTransactionIngestWorker", "RawTransactionIngestWorker",
"RawTransactionIngestYellowstoneSource",
] ]
); );
assert!(!root.contains("pub mod ")); assert!(!root.contains("pub mod "));
@@ -92,22 +98,82 @@ fn pre_010_external_hardening_suite_is_present_and_scoped() {
for required in [ for required in [
"pre_010_external_error_codes_are_stable_unique_and_domain_scoped", "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_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_source_visibility_contract_uses_crate_root_for_shared_items",
"pre_010_production_surface_has_no_historical_backfill_or_retriever_contract", "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_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",
] { ] {
assert!(hardening.contains(required), "required pre.010 hardening canary missing: {required}"); assert!(hardening.contains(required), "required pre.010 hardening canary missing: {required}");
} }
let dependency_boundary = include_str!("dependency_boundary.rs"); let dependency_boundary = include_str!("dependency_boundary.rs");
assert!(dependency_boundary.contains("pre_002_manifest_dependency_surface_is_exact")); 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"));
let public_api = include_str!("public_api.rs"); 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_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_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_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("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_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; 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(());
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/snapshot.rs // file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/snapshot.rs
// version: 2 // version: 4
fn snapshot_foundation() -> std::option::Option<(crate::RawTransactionIngestSnapshotPublisher, crate::RawTransactionIngestSnapshotSource)> { fn snapshot_foundation() -> std::option::Option<(crate::RawTransactionIngestSnapshotPublisher, crate::RawTransactionIngestSnapshotSource)> {
let network = match ksp_store_lib::RawNetworkId::new("mainnet") { let network = match ksp_store_lib::RawNetworkId::new("mainnet") {
@@ -50,6 +50,13 @@ fn pre_008_initial_snapshot_and_common_projection_are_exact() {
assert_eq!(concrete.store_failure_total(), 0); assert_eq!(concrete.store_failure_total(), 0);
assert_eq!(concrete.source_failure_total(), 0); assert_eq!(concrete.source_failure_total(), 0);
assert_eq!(concrete.backpressure_wait_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_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); let common = ksp_worker_api::WorkerSnapshotSource::current(&source);
assert_eq!(&common, concrete.worker_snapshot()); assert_eq!(&common, concrete.worker_snapshot());
return; return;
@@ -134,3 +141,61 @@ fn pre_009_source_failure_and_backpressure_counters_advance_without_changing_pro
assert_eq!(&common, concrete.worker_snapshot()); assert_eq!(&common, concrete.worker_snapshot());
return; 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;
}

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.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/004-COMPONENT_INVENTORY.md --> <!-- file: docs/architecture/004-COMPONENT_INVENTORY.md -->
<!-- version: 38 --> <!-- version: 39 -->
# Inventaire initial des composants KSP # Inventaire initial des composants KSP
@@ -46,7 +46,7 @@ Ce document maintient l'inventaire synthétique des composants retenus ou presse
| Store Desk | `ksp-app-store-desk` | app | Implémenté | `0.3.8` | inspection RAW read-only Transaction/Account/Observation via façade Store | | Store Desk | `ksp-app-store-desk` | app | Implémenté | `0.3.8` | inspection RAW read-only Transaction/Account/Observation via façade Store |
| RAW transaction common | `ksp-raw-transaction-lib` | lib | Implémenté | `0.3.10` | canonicalisation/wire RAW Transaction v1 source-neutral partagé entre producteurs | | RAW transaction common | `ksp-raw-transaction-lib` | lib | Implémenté | `0.3.10` | canonicalisation/wire RAW Transaction v1 source-neutral partagé entre producteurs |
| Worker lifecycle | `ksp-worker-api` | API | Implémenté | `0.3.9` | lifecycle/health/progression génériques des services continus | | Worker lifecycle | `ksp-worker-api` | API | Implémenté | `0.3.9` | lifecycle/health/progression génériques des services continus |
| RAW transaction worker | `ksp-worker-raw-transaction-ingest-lib` | worker/lib | Implémenté | `0.3.11`, extensions `0.3.12+` | fondation source-neutral, puis sources live multi-source/recovery | | RAW transaction worker | `ksp-worker-raw-transaction-ingest-lib` | worker/lib | Implémenté | `0.3.11`, live `0.3.12` | fondation + Yellowstone productif, hydration HTTP, frontier/reconnect run-local |
| RAW ingest Desk | `ksp-app-raw-transaction-ingest-desk` | app | Retenu | `0.3.15` | choix/supervision dune ou plusieurs sources/méthodes sans réimplémenter le worker | | RAW ingest Desk | `ksp-app-raw-transaction-ingest-desk` | app | Retenu | `0.3.15` | choix/supervision dune ou plusieurs sources/méthodes sans réimplémenter le worker |
| STRUCTURAL job | nom à fixer | job/lib | Retenu | couche STRUCTURAL | normalisation Solana générique RAW -> STRUCTURAL bornée/rejouable | | STRUCTURAL job | nom à fixer | job/lib | Retenu | couche STRUCTURAL | normalisation Solana générique RAW -> STRUCTURAL bornée/rejouable |
| STRUCTURAL worker | nom à fixer | worker/lib | Retenu | fin couche STRUCTURAL | backlog RAW -> STRUCTURAL continu | | STRUCTURAL worker | nom à fixer | worker/lib | Retenu | fin couche STRUCTURAL | backlog RAW -> STRUCTURAL continu |

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/005-DEPENDENCY_GRAPH.md --> <!-- file: docs/architecture/005-DEPENDENCY_GRAPH.md -->
<!-- version: 27 --> <!-- version: 28 -->
# Graphe de dépendances KSP # Graphe de dépendances KSP
@@ -419,9 +419,10 @@ Il remplit RAW et ne décode aucun programme. Il ne dépend pas de Config : la c
ksp-worker-api ksp-worker-api
-> ksp-core-lib -> ksp-core-lib
ksp-worker-raw-transaction-ingest-lib # fondation source-neutral matérialisée ksp-worker-raw-transaction-ingest-lib # fondation + première source live matérialisées
-> ksp-core-lib -> ksp-core-lib
-> ksp-logging-lib -> ksp-logging-lib
-> ksp-onchain-transport-lib # Yellowstone subscribe + HTTP getTransaction observed
-> ksp-raw-transaction-lib -> ksp-raw-transaction-lib
-> ksp-store-lib # default-features = false ; aucun backend imposé -> ksp-store-lib # default-features = false ; aucun backend imposé
-> ksp-worker-api -> ksp-worker-api
@@ -432,9 +433,9 @@ ksp-worker-control-lib # composant retenu, non matérialis
-> ksp-core-lib -> ksp-core-lib
``` ```
`ksp-worker-api` est ouvert en `0.3.9` comme contrat générique de services continus et ne connaît ni Solana, ni Transport, ni Store, ni Tauri. `ksp-worker-raw-transaction-ingest-lib` est son premier consumer concret : sa fondation `0.3.11` est source-neutral, possède admission/persistence/snapshots/shutdown, et ne dépend pas encore de Transport. Les adapters live sont ajoutés ensuite sans déplacer Config ni backend physique dans le Worker. `ksp-worker-api` est ouvert en `0.3.9` comme contrat générique de services continus et ne connaît ni Solana, ni Transport, ni Store, ni Tauri. `ksp-worker-raw-transaction-ingest-lib` est son premier consumer concret : sa fondation `0.3.11` possède admission/persistence/snapshots/shutdown ; l'extension live `0.3.12` ouvre l'edge direct vers `ksp-onchain-transport-lib` pour une source Yellowstone standard et l'hydration HTTP `getTransaction`. Config et le backend physique restent hors du Worker.
La cible live n'est pas « un worker WebSocket » ou « un worker gRPC ». Les tranches de sources peuvent ajouter `ksp-onchain-transport-lib` lorsque l'adapter productif le nécessite et composer des stratégies alternatives, complémentaires (discovery + hydration), redondantes entre providers ou spécialisées live/catch-up/gap-repair. La transaction canonique reste identifiée indépendamment de la source et chaque acquisition utile conserve sa propre observation/provenance Store. La verticale actuelle n'est ni « un worker WebSocket » ni un moteur historique : `RawTransactionIngestRuntimeResources` contient exactement une source Yellowstone validée, les signaux transactionnels sont hydratés par HTTP avant Common RAW, et `BlockMeta`/`Slot` restent continuity-only. Reconnect, `from_slot` et `SubscribeReplayInfo` restent propriétaires de Transport ; le Worker observe leur projection sûre et fault sur un gap de rétention prouvé sans appeler le Job Backfill. Les extensions multi-source restent ultérieures.
RAW worker puis STRUCTURAL worker sont introduits à la fin de leur couche respective, lorsque persistence/backlog sont disponibles. Le traitement RAW -> STRUCTURAL borné est porté par un STRUCTURAL job distinct du service continu. Les workers DECODED/DOMAIN sont introduits avec les groupes Program concernés plutôt que tous anticipés en bloc. RAW worker puis STRUCTURAL worker sont introduits à la fin de leur couche respective, lorsque persistence/backlog sont disponibles. Le traitement RAW -> STRUCTURAL borné est porté par un STRUCTURAL job distinct du service continu. Les workers DECODED/DOMAIN sont introduits avec les groupes Program concernés plutôt que tous anticipés en bloc.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md --> <!-- file: docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md -->
<!-- version: 18 --> <!-- version: 19 -->
# Acquisition, workers, jobs et pipelines spécialisés # Acquisition, workers, jobs et pipelines spécialisés
@@ -102,7 +102,7 @@ ksp-worker-raw-transaction-ingest-lib
Sa responsabilité est l'acquisition **continue** de `RawTransaction` puis la persistance via `ksp-store-lib`. Il ne décode pas de Program, ne possède aucun SQL/backend physique et ne fait pas de la source réseau une partie de l'identité canonique de transaction. Sa responsabilité est l'acquisition **continue** de `RawTransaction` puis la persistance via `ksp-store-lib`. Il ne décode pas de Program, ne possède aucun SQL/backend physique et ne fait pas de la source réseau une partie de l'identité canonique de transaction.
La fondation matérialisée possède déjà le lifecycle continu, les settings techniques bornés, la queue d'admission privée bornée, la canonicalisation Common RAW, la persistance atomique backend-neutral, les snapshots latest-value et le shutdown borné. Elle ne possède encore aucune source réseau productive et n'a donc aucun edge Transport ; les adapters live/catch-up complètent ensuite ce même Worker sans ouvrir de second runtime parallèle. Le Worker matérialisé possède le lifecycle continu, les settings techniques bornés, la queue d'admission privée, la canonicalisation Common RAW, la persistance atomique backend-neutral, les snapshots latest-value et le shutdown borné. Sa première source productive est désormais Yellowstone standard + hydration HTTP `getTransaction`, injectée par `RawTransactionIngestRuntimeResources` sans lecture Config interne. `Transaction`, `TransactionStatus` et les transactions de `Block` deviennent des signaux coalescés puis hydratés ; `BlockMeta` et `Slot` restent continuity-only. Le Worker n'ouvre pas un second runtime parallèle et n'expose toujours aucune API publique d'enqueue.
Le modèle cible n'est pas : Le modèle cible n'est pas :
@@ -131,18 +131,13 @@ normalisation RawTransaction commune
ksp-store-lib ksp-store-lib
``` ```
Une stratégie peut donc être : Le modèle général autorise des stratégies alternatives, complémentaires ou redondantes, mais la verticale productive actuelle est volontairement plus étroite : **une** source Yellowstone + **une** voie HTTP d'hydration. Ce choix n'inscrit pas le provider ou le protocole dans l'identité RAW et ne ferme pas l'architecture future multi-source.
- **alternative** : une source choisie à la place d'une autre ; Le reconnect/replay du stream est une capacité Transport. Le Worker conserve une processing frontier run-local sur le travail réellement observé, projette `Active/Reconnecting/Closing/Closed/Failed` et des compteurs reconnect/replay/gap, mais ne possède ni checkpoint durable ni campagne de gap repair. Un gap de rétention prouvé par Transport devient un fault `source_failed` ; une récupération historique éventuelle reste une responsabilité séparée du Job Backfill.
- **complémentaire** : une source découvre une signature/slot et une autre hydrate la transaction complète ;
- **redondante** : plusieurs providers/transports observent la même transaction et produisent des observations distinctes ;
- **spécialisée** : une source live, une voie d'hydration et une voie de continuité/gap repair du run peuvent coexister avec des responsabilités différentes.
Le worker ne doit pas être réduit à un enum superficiel `Http | WebSocket | Grpc`. La configuration/runtime doit exprimer les **capacités et rôles d'acquisition réellement nécessaires** : discovery, hydration, direct full transaction, live, replay/catch-up, gap repair, filtre, finality/commitment, reprise et limites.
#### Résultat de l'audit `0.3.9` #### Résultat de l'audit `0.3.9`
L'audit exhaustif est synthétisé dans [`011-RAW_TRANSACTION_ACQUISITION.md`](011-RAW_TRANSACTION_ACQUISITION.md). Il confirme qu'un besoin d'acquisition Solana/provider ne remonte pas dans `ksp-worker-api` : le contrat générique reste fermé et le Worker concret porte ses propres capabilities de sources, sans les ouvrir dans sa fondation source-neutral. L'audit exhaustif est synthétisé dans [`011-RAW_TRANSACTION_ACQUISITION.md`](011-RAW_TRANSACTION_ACQUISITION.md). Il confirme qu'un besoin d'acquisition Solana/provider ne remonte pas dans `ksp-worker-api` : le contrat générique reste fermé et le Worker concret porte ses capabilities de source dans sa propre implémentation/runtime resources.
Les familles admises par la synthèse couvrent notamment : Les familles admises par la synthèse couvrent notamment :

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md --> <!-- file: docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md -->
<!-- version: 12 --> <!-- version: 13 -->
# Acquisition et alimentation `RawTransaction` # Acquisition et alimentation `RawTransaction`
@@ -149,7 +149,7 @@ Il peut utiliser **HTTP, WS, Yellowstone gRPC, replay provider ou archive** si c
### 3.2 Worker Raw Transaction Ingest : acquisition continue start/stop ### 3.2 Worker Raw Transaction Ingest : acquisition continue start/stop
`ksp-worker-raw-transaction-ingest-lib` a pour rôle cible de **remplir continuellement Store à partir du moment où il est démarré**. Sa fondation source-neutral matérialise déjà le runtime, l'admission, la canonicalisation Common RAW, la persistence et les snapshots, mais aucune source réseau productive n'est encore branchée. `ksp-worker-raw-transaction-ingest-lib` a pour rôle de **remplir continuellement Store à partir du moment où il est démarré**. Sa fondation runtime, l'admission, la canonicalisation Common RAW, la persistence et les snapshots sont complétés par une première source productive Yellowstone standard + hydration HTTP `getTransaction`.
Son contrôle métier V1 est volontairement court : Son contrôle métier V1 est volontairement court :
@@ -161,43 +161,49 @@ snapshot / notifications
Le caller ne lui fournit pas une signature, un `program_id`, une plage historique, une limite de campagne ou une requête de backfill. Les endpoints, réseaux, sources activées, capabilities et secrets proviennent de Config/composition, pas d'un payload métier de `start`. Le caller ne lui fournit pas une signature, un `program_id`, une plage historique, une limite de campagne ou une requête de backfill. Les endpoints, réseaux, sources activées, capabilities et secrets proviennent de Config/composition, pas d'un payload métier de `start`.
La fondation actuelle, lorsqu'elle démarre sans adapter source productif, publie son lifecycle/snapshot puis reste contrôlable jusqu'au stop. Dès qu'une source interne est matérialisée, le pipeline cible devient : Deux entrées publiques coexistent : `start` conserve la fondation sans source productive, tandis que `start_with_runtime_resources` lance une source Yellowstone supervisée. Le pipeline productif actuel est :
```text ```text
acquiert les nouvelles transactions disponibles Yellowstone Transaction / TransactionStatus / Block
hydrate les signaux live incomplets si nécessaire -> signaux transactionnels source-neutral
normalise puis persiste RawTransaction + observations -> coalescence bornée (network, signature, commitment)
publie ses snapshots latest-value indépendamment de leurs lecteurs -> HTTP getTransaction observed
continue jusqu'à stop ou fault -> Common RAW
-> admission centrale
-> Store
Yellowstone BlockMeta / Slot
-> continuité run-local uniquement
``` ```
Le Worker peut ensuite utiliser **WS, Yellowstone gRPC, HTTP, block polling, provider streams ou sources EARLY** si ces capacités servent l'acquisition live. Ces sources restent internes au Worker ; le caller ne pousse pas directement des ingress dans sa queue privée. La source est caller-composed : le Worker ne lit pas Config et ne reçoit pas de secret. `RawTransactionIngestRuntimeResources` contient actuellement exactement une source Yellowstone validée, pas encore une collection multi-source.
Il ne lance pas de campagne historique arbitraire. Il ne lance pas de campagne historique arbitraire.
### 3.3 Continuité Worker ≠ Backfill ### 3.3 Continuité Worker ≠ Backfill
Le Worker peut utiliser un replay ou HTTP pour **réparer une perte de continuité apparue pendant son acquisition active** : La continuité du Worker reste limitée au run courant. Le moteur Yellowstone de Transport possède le reconnect et peut réémettre une demande `from_slot` à partir de son high-watermark observé ; le Worker ne choisit pas lui-même ce slot et ne traite pas directement `SubscribeReplayInfo`.
Les notions restent séparées :
```text ```text
stream actif Transport last_observed_slot = high-watermark du stream observé
-> gap détecté Worker processing_frontier_slot = travail source observé et non bloqué par un pending plus ancien
-> replay from_slot / HTTP hydration / block recovery Store durable = persistence des acquisitions admises
-> frontier live restauré blockchain completeness = non prouvée par les métriques ci-dessus
-> reprise du flux
``` ```
Cette réparation reste liée au frontier du Worker et ne transforme pas le Worker en moteur historique. Une tentative de replay ne prouve ni succès ni continuité parfaite. Si Transport prouve qu'un slot demandé est antérieur à `first_available`, son compteur de continuity gap augmente ; le Worker projette ce gap puis termine avec un fault `source_failed`.
Inversement : Il n'existe pas de délégation automatique vers Backfill :
```text ```text
« récupère les transactions du programme X depuis le slot Y » gap de rétention prouvé
« récupère les 100 000 dernières signatures de cette adresse » -> Worker fault/stop
« rejoue cette plage d'archive » -> aucun lancement de campagne historique
``` ```
sont des campagnes Job Backfill. Inversement, une demande telle que « récupère les transactions du programme X depuis le slot Y » reste une campagne `ksp-job-backfill-lib`.
### 3.4 Absence de relation Job ↔ Worker ### 3.4 Absence de relation Job ↔ Worker
@@ -657,13 +663,13 @@ ksp-worker-raw-transaction-ingest-lib ---> ksp-raw-transaction-lib ----> ksp-sto
ksp-job-backfill-lib --------------------> ksp-store-lib ksp-job-backfill-lib --------------------> ksp-store-lib
ksp-worker-raw-transaction-ingest-lib ---> ksp-store-lib ksp-worker-raw-transaction-ingest-lib ---> ksp-store-lib
ksp-job-backfill-lib --------------------> ksp-onchain-transport-lib ksp-job-backfill-lib --------------------> ksp-onchain-transport-lib
ksp-worker-raw-transaction-ingest-lib -X-> ksp-onchain-transport-lib # aucun adapter live productif encore ksp-worker-raw-transaction-ingest-lib ---> ksp-onchain-transport-lib # Yellowstone + HTTP hydration
aucun edge Job <-> Worker aucun edge Job <-> Worker
aucun edge Transport <-> ksp-raw-transaction-lib aucun edge Transport <-> ksp-raw-transaction-lib
``` ```
Les tranches live suivantes peuvent matérialiser l'edge Worker -> Transport lorsqu'un adapter source productif le nécessite ; cet edge n'appartient pas à la fondation source-neutral. L'edge Worker -> Transport est désormais productif, mais reste strictement contenu dans le Worker concret. `ksp-worker-api`, Common RAW et Store API ne gagnent aucune connaissance Transport/provider.
La migration doit préserver exactement les golden bytes/hash RAW v1 déjà prouvés. Aucun RAW v2 n'est justifié. La migration doit préserver exactement les golden bytes/hash RAW v1 déjà prouvés. Aucun RAW v2 n'est justifié.
@@ -713,13 +719,13 @@ Yellowstone Transaction/Block -> signal structuré + transaction wire fidèle
RAW v1 complet -> hydration HTTP avant persistence RAW v1 complet -> hydration HTTP avant persistence
``` ```
Aucun adapter productif n'est ajouté par la qualification cross-source. Conformément à `TR-C2`, l'adapter `Transport DTO -> common` demeure réservé au Worker concret. La fondation `0.3.11` est désormais matérialisée sans Transport ; le premier adapter productif reste donc une responsabilité des tranches live suivantes. Cette qualification cross-source a préparé le contrat sans créer d'edge Common RAW -> Transport. L'extension Worker live utilise désormais cette décision conservative : Yellowstone produit des signaux structurés dans le Worker concret, puis HTTP `getTransaction` fournit le matériau RAW complet avant canonicalisation. Common RAW reste entièrement Transport-neutral.
## 14. Handoff fondation Worker `0.3.11` vers live `0.3.12` à `0.3.14` ## 14. État du Worker live après `0.3.12`
### 14.0 État fermé par la fondation source-neutral ### 14.0 Verticale matérialisée
La fondation du Worker est matérialisée avec : La verticale productive possède :
```text ```text
consumer de ksp-worker-api consumer de ksp-worker-api
@@ -727,26 +733,33 @@ settings réseau/Worker bornés
start/stop sur runtime Tokio caller-owned start/stop sur runtime Tokio caller-owned
supervision privée des tâches supervision privée des tâches
mpsc central borné + backpressure mpsc central borné + backpressure
une source Yellowstone standard caller-composed
Transaction / TransactionStatus / Block -> signaux transactionnels
BlockMeta / Slot -> continuity-only
coalescence bornée par network/signature/commitment
HTTP getTransaction observed pour hydration
canonicalisation et assembly via ksp-raw-transaction-lib canonicalisation et assembly via ksp-raw-transaction-lib
observation key déterministe
persistence atomique via ksp-store-lib en mode Normal persistence atomique via ksp-store-lib en mode Normal
concurrence Store bornée processing frontier run-local bornée
snapshots concrete + projection WorkerSnapshotSource projection source Active/Reconnecting/Closing/Closed/Failed
faults Store/content/source/counter classifiés observation des compteurs reconnect/replay/gap Transport
fault sur gap de rétention prouvé
shutdown deadline + abort/join sans tâche orpheline shutdown deadline + abort/join sans tâche orpheline
``` ```
Elle ne possède encore : Elle conserve explicitement les frontières suivantes :
```text ```text
aucun adapter HTTP/WS/Yellowstone productif aucune lecture Config dans le Worker
aucun edge ksp-onchain-transport-lib aucun backend Store physique
aucune lecture Config
aucune API publique d'enqueue/source registration aucune API publique d'enqueue/source registration
aucun replay/gap-repair live aucun checkpoint persistant de processing frontier
aucun appel Worker -> ksp-job-backfill-lib
aucune campagne historique automatique sur continuity gap
aucun client reqwest/tonic/proto direct hors façade Transport
``` ```
Cette frontière est volontaire : les tranches live ajoutent des sources au supervisor existant au lieu d'introduire un second runtime ou une API d'admission publique. Le replay reconnect est Transport-owned. La processing frontier ne constitue pas une preuve de complétude durable ou blockchain.
### 14.1 Contrat fonctionnel ### 14.1 Contrat fonctionnel
@@ -755,12 +768,12 @@ Cette frontière est volontaire : les tranches live ajoutent des sources au supe
```text ```text
être un consumer de ksp-worker-api être un consumer de ksp-worker-api
être démarré/arrêté sans requête historique métier être démarré/arrêté sans requête historique métier
ouvrir les sources activées par Config recevoir des ressources déjà composées par la couche supérieure
acquérir à partir du démarrage acquérir à partir du démarrage
supporter plusieurs sources simultanées
normaliser et persister RawTransaction + observations normaliser et persister RawTransaction + observations
publier snapshots/notifications concrètes Worker publier snapshots/notifications concrètes Worker
réparer seulement ses propres pertes de continuité live laisser reconnect/from_slot/replay au Transport
fault proprement lorsqu'un gap de rétention est prouvé
``` ```
Il ne doit pas : Il ne doit pas :

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,638 @@
<!-- file: prompts/032-V0_3_13_START_PROMPT.md -->
<!-- version: 1 -->
# Prompt de démarrage `0.3.13` — WS standard / Helius / HTTP live + convergence multi-source du Worker `RawTransaction`
## 1. Identité de la release et base exacte requise
Ouvrir **uniquement** `0.3.13` depuis la release stable/taggée :
```text
v0.3.12
```
La base de travail fournie par l'opérateur est autoritaire sur les souvenirs, snippets, anciennes archives et deltas intermédiaires. Avant toute modification, vérifier au minimum :
```text
workspace.package.version = 0.3.12
deltas/0.3.12/rel.001.md présent
crates/ksp-worker-raw-transaction-ingest-lib présent avec source Yellowstone productive
ksp-worker-raw-transaction-ingest-lib -> ksp-onchain-transport-lib matérialisé
ksp-worker-raw-transaction-ingest-lib sans dépendance Config/Job/backend Store direct
ksp-raw-transaction-lib présent comme Common RAW source-neutral
ksp-store-lib reste la seule façade Store du Worker
moteur WS standard/Helius existant dans ksp-onchain-transport-lib
getTransaction/getBlock observed existants dans ksp-onchain-transport-lib
```
Release ouverte :
```text
0.3.13
```
Première livraison attendue :
```text
0.3.13-pre.001
```
`pre.001` est obligatoirement un gate de **lecture + audit interne/externe + brainstorming + sizing + planification**. Il ne commence pas directement les sources WS/Helius/HTTP polling ni la transformation de `RawTransactionIngestRuntimeResources` en collection multi-source. Si le périmètre réel n'est pas clôturable dans une seule session ou si une tranche paraît dépasser environ 1520 minutes de travail effectif, rescinder avant l'implémentation lourde.
## 2. Mission et résultat attendu
### 2.1 Mission de `0.3.13`
Étendre la verticale `0.3.12` avec les voies live complémentaires déjà qualifiées par l'architecture :
```text
WS standard logsSubscribe + hydration HTTP
WS standard blockSubscribe
Helius transactionSubscribe + hydration HTTP
HTTP live polling par slots/blocs
convergence simultanée multi-source vers le pipeline central existant
observations multiples pour une même RawTransaction
coalescence/déduplication bornée avant hydration/admission
content conflict explicite si une même identité produit un contenu canonique divergent
backpressure et source health sous concurrence multi-source
```
Le résultat visé est un Worker capable de superviser plusieurs sources live indépendantes **sans second actor Transport**, sans lire Config lui-même et sans transformer la convergence en stratégie `first-provider-wins` silencieuse.
### 2.2 Pipeline durable à préserver
Toutes les sources convergent vers le même cœur :
```text
source live
-> signal ou matériau source/protocole
-> hydration si le RAW complet n'est pas prouvé
-> ksp-raw-transaction-lib
-> RawTransaction + RawTransactionObservation
-> admission centrale Worker
-> ksp-store-lib
```
Décisions déjà acquises :
```text
logsSubscribe -> discovery/signature -> getTransaction hydration
Helius transactionSubscribe -> matériau riche mais hydration tant que blockTime/version manquent
blockSubscribe full/base64 -> RAW-direct uniquement sur le sous-ensemble déjà prouvé par fixtures
HTTP polling -> getSlot/bornes -> getBlock observed -> transactions -> Common RAW
même identité + même contenu -> idempotence + observations distinctes utiles
même identité + contenu divergent -> content conflict explicite
```
Aucune source ne doit contourner Common RAW ou la façade Store.
## 3. Sources de vérité internes obligatoires — ordre de lecture
### 3.1 Gouvernance générale
Lire intégralement, dans cet ordre :
```text
RULES.md
ROADMAP.md
CHANGELOG.md
docs/000-README.md
docs/rules/RULES_GENERAL.md
docs/rules/RULES_KSP.md
docs/rules/RULES_RUST.md
docs/rules/RULES_DEPENDENCIES.md
docs/rules/RULES_DOCUMENTATION.md
docs/rules/FILE_CONTRACTS.md
docs/rules/VERSION_WORKFLOW.md
docs/rules/PROMPT_STRUCTURE.md
```
Le prompt complète ces règles ; il ne les remplace pas.
Rappels Rust bloquants :
```text
Rust 2024
unsafe interdit
unwrap / expect / panic interdits selon les règles KSP
? interdit en production
retours explicites ; clippy::implicit_return deny
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
pas de pub mod
pub/pub(crate) partagés reexportés jusqu'à la crate root
accès partagés via crate::Item, y compris intra-crate
item strictement module-local => private
unit tests sous unit_tests/
integration tests sous tests/
```
Après toute modification Rust :
```bash
cargo fmt --all
python3 scripts/audit_rust_workspace_rules.py
cargo check --workspace
cargo clippy --workspace --all-targets
```
Pour tout Markdown touché :
```bash
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas
```
Une commande non exécutée n'est jamais déclarée PASS.
### 3.2 Handoff stable `0.3.12`
Lire intégralement :
```text
docs/plans/033-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY_PLAN.md
docs/validation/029-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY.md
crates/ksp-worker-raw-transaction-ingest-lib/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/
crates/ksp-worker-raw-transaction-ingest-lib/tests/
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/
```
La verticale Yellowstone/hydration/frontier est une base à préserver, pas un prototype à remplacer.
### 3.3 Architecture RAW et convergence
Lire intégralement :
```text
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
crates/ksp-raw-transaction-lib/README.md
crates/ksp-raw-transaction-lib/USAGE.md
crates/ksp-store-api/src/
crates/ksp-store-lib/src/
```
Porter une attention particulière à :
```text
observations multiples ≠ identité RawTransaction
même identité + contenu divergent = conflit explicite
capabilities orthogonales aux producteurs
WS logsSubscribe
WS blockSubscribe
Helius transactionSubscribe
HTTP getSlot/getBlock live polling
provenance observed
reconnect WS != replay adressable
continuité multi-source finale encore reportée à 0.3.14
```
### 3.4 Transport WS / HTTP / Helius existant
Lire au minimum :
```text
crates/ksp-onchain-transport-lib/Cargo.toml
crates/ksp-onchain-transport-lib/README.md
crates/ksp-onchain-transport-lib/USAGE.md
crates/ksp-onchain-transport-lib/src/ws_*.rs
crates/ksp-onchain-transport-lib/src/rpc_transactions.rs
crates/ksp-onchain-transport-lib/src/rpc_blocks.rs
crates/ksp-onchain-transport-lib/src/http_*.rs
crates/ksp-onchain-transport-lib/tests/
```
Réutiliser les sessions/actors Transport existants. Il est interdit de recréer dans le Worker :
```text
nouveau socket WebSocket bas niveau
client Helius parallèle
client reqwest direct
reconnect/resubscribe actor concurrent
SDK provider
```
### 3.5 Config et secrets
Lire :
```text
crates/ksp-config-lib/src/transport.rs
config/transport*.json
config/schema/*transport*.json
.env.example
```
Config reste le seul propriétaire des endpoints, profils, capabilities et secrets. En particulier, si Helius est activé pour cette release, réutiliser exclusivement le secret Config déjà défini pour Helius ; le Worker ne lit jamais l'environnement et ne reçoit jamais une clé API comme payload métier.
## 4. Sources externes normatives et fraîcheur à réauditer
Au début de `pre.001`, réauditer les surfaces courantes à partir des sources primaires réellement actuelles :
```text
Solana RPC/WebSocket documentation
logsSubscribe
blockSubscribe
getSlot / getBlocks / getBlocksWithLimit / getBlock
commitment et maxSupportedTransactionVersion
Helius documentation officielle
transactionSubscribe
filtres/options réellement supportés
Mainnet/Devnet disponibles
tier/quota/rate limits actuels
champs blockTime/version réellement présents ou absents
crates/projets primaires réellement concernés
versions stables courantes
features nécessaires
```
Ne pas figer dans le plan une limite provider historique sans la revalider. Une différence entre documentation externe actuelle et surface KSP existante devient un point d'audit explicite, pas une permission de contourner Transport.
## 5. État validé à préserver
`0.3.12` fournit déjà :
```text
Worker concret start/stop + supervisor
admission centrale mpsc bornée
persistence Store atomique/idempotente
Common RAW Legacy/V0/V1
une source Yellowstone caller-composed
Transaction / TransactionStatus / Block -> hydration transactionnelle
BlockMeta / Slot -> continuity-only
coalescence bornée network/signature/commitment
getTransaction observed
processing frontier run-local
source state Active/Reconnecting/Closing/Closed/Failed
compteurs reconnect/replay-attempt/continuity-gap
fault sur gap de rétention prouvé
shutdown borné + abort/join
cross-layer dependency/security canaries
```
Frontières non négociables :
```text
Worker -> Config interdit
Worker -> Job Backfill interdit
Worker -> backend Store physique interdit
Transport -> Common RAW interdit
Common RAW -> Transport interdit
pas d'URL/token/provider payload dans snapshot/debug/error
pas de checkpoint persistant de processing frontier
pas de campagne historique arbitraire dans le Worker
```
Le gate `0.3.12` a aussi prouvé live HTTP Devnet et WebSocket Devnet keyless ; il n'a pas prouvé live Yellowstone ni un Worker end-to-end avec Store. Ne pas transformer ces non-preuves en hypothèses acquises.
## 6. Décisions acquises et questions réellement ouvertes
### 6.1 Décisions acquises
```text
une RawTransaction est identifiée par (network, signature)
la provenance appartient aux observations, pas à l'identité canonique
plusieurs sources peuvent produire plusieurs observations du même RAW
content conflict reste terminal/explicite, jamais first-provider-wins silencieux
logsSubscribe nécessite hydration
Helius transactionSubscribe nécessite hydration tant que son enveloppe reste incomplète pour RAW v1
blockSubscribe direct reste limité au sous-ensemble réellement qualifié
HTTP polling est une source live valide s'il suit uniquement le frontier du run courant
WS reconnect/resubscribe n'est pas un replay adressable
repair de gap multi-source final appartient à 0.3.14
```
### 6.2 Questions à fermer par `pre.001`
```text
forme exacte de RawTransactionIngestRuntimeResources multi-source
identité/source key interne commune aux sources live
activation simultanée vs priorités/fallback : sémantique exacte
coalescence cross-source avant ou après hydration selon matériau disponible
comment conserver plusieurs observations sans dupliquer la canonicalisation
blockSubscribe : sous-ensembles Legacy/V0/V1 réellement directs aujourd'hui
HTTP polling : borne initiale, cadence, slot skip et ownership du timer
source health individuel vs agrégation Worker publique
limites exactes de pending/coalescence par source et globales
Helius : profils/tier réellement testables avec les comptes opérateur disponibles
```
Aucune de ces questions ne doit être tranchée par un ajout opportuniste pendant le codage.
## 7. Objectifs/livrables et hors périmètre
### 7.1 Livrables cibles
Sous réserve du sizing `pre.001` :
```text
contrat runtime resources réellement multi-source et borné
source WS logsSubscribe productive + hydration
source WS blockSubscribe productive
source Helius transactionSubscribe productive + hydration
source HTTP live block polling productive
convergence vers la même admission/persistence centrale
coalescence et observation semantics cross-source
source health/backpressure/faults cohérents
fixtures déterministes cross-source
smokes live uniquement lorsqu'ils sont réellement accessibles
README/USAGE/architecture réconciliés à la fin
```
### 7.2 Hors périmètre `0.3.13`
```text
gap repair multi-source complet
replay historique arbitraire
appel automatique au Backfill
checkpoint durable Worker
source archive/historical provider
EARLY/pre-execution/shreds
nouveau SDK provider
nouveau backend Store ou migration Store
nouvelle Desk d'ingestion
extension du Job Backfill
```
Ces éléments appartiennent à `0.3.14+` ou `0.3.16` selon la roadmap.
## 8. Contraintes sécurité/API/architecture spécifiques
La convergence multi-source doit rester bornée :
```text
aucune Vec/Map de pending non bornée
aucun task spawn non détenu
aucun retry loop Worker redondant avec Transport
aucune queue par provider sans borne explicite
aucun drop silencieux sous saturation
aucun changement de source qui efface une provenance déjà acquise
```
La sémantique de conflit reste :
```text
même identité + même canonical RAW
-> idempotence
-> observation supplémentaire si sa clé est distincte
même identité + canonical RAW divergent
-> content conflict explicite
-> arrêt/fault selon contrat Worker existant
```
Le tracing ne doit jamais inclure :
```text
signature brute
transaction/meta bytes
logs provider
URL endpoint
API key/x-token
filtres Helius sensibles
remote error text non borné
```
Toute surface publique ajoutée doit être justifiée par un consumer réel. Préférer les types privés/crate-private et les snapshots source-neutral aux DTO provider-specific publics.
## 9. Première mission `pre.001` — audit/sizing obligatoire
`pre.001` doit produire avant toute implémentation lourde :
1. vérification exacte de la base stable `v0.3.12` et de `deltas/0.3.12/rel.001.md` ;
2. lecture complète des sources internes listées ci-dessus ;
3. inventaire exact des acteurs/sessions/facades WS standard, Helius et HTTP existants ;
4. audit externe courant Solana/Helius et versions de crates réellement concernées ;
5. matrice par source : signal/material, RAW-direct ou hydration, provenance, reconnect, network, tier/preuve live ;
6. architecture cible de `RawTransactionIngestRuntimeResources` multi-source sans Worker -> Config ;
7. stratégie de source identity, source lifecycle et agrégation health ;
8. stratégie de coalescence cross-source et de production d'observations multiples ;
9. stratégie HTTP polling live bornée depuis le démarrage du run, sans campagne historique ;
10. threat model : duplicate storms, source skew, provider disagreement, hydration fanout, reconnect storms, polling drift, slow Store, stop/fault concurrent ;
11. inventaire des smokes réellement accessibles avec les ressources opérateur ;
12. graphes Cargo/features cibles et éventuels changements Config strictement nécessaires ;
13. sizing de chaque tranche sous le budget 1520 minutes ;
14. décision explicite : maintien de `0.3.13` ou rescission avant codage ;
15. création du plan et de la validation de release.
Documents attendus :
```text
docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md
docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md
```
Critère de sortie : chaque source retenue et la convergence doivent avoir un contrat d'entrée/sortie, des bornes, une stratégie de preuve et un propriétaire de retry/reconnect clairement identifiés avant `pre.002`.
## 10. Prévision souple initiale des prereleases
Cette prévision est un point de départ à recalibrer par `pre.001`.
### `pre.001` — audit / brainstorming / sizing
Base stable, audit WS/Helius/HTTP courant, matrice de matériau/hydration, architecture multi-source, threat model, smokes, graphes, plan/validation et décision de maintien/rescission.
### `pre.002` — contrat runtime resources multi-source
Généraliser la composition caller-owned vers plusieurs sources bornées, avec identité interne/source lifecycle minimale, sans brancher encore toutes les voies live.
### `pre.003` — WS standard `logsSubscribe` + hydration
Brancher la discovery signature/logs via la façade Transport existante et fermer `logs -> getTransaction observed -> Common RAW` sans recopier l'actor WS.
### `pre.004` — WS standard `blockSubscribe`
Brancher les blocs full/base64 et utiliser RAW-direct uniquement pour les versions réellement qualifiées ; hydration/fault explicite ailleurs.
### `pre.005` — Helius `transactionSubscribe`
Réutiliser la surface Transport Helius existante, appliquer l'hydration nécessaire et les profils Config réellement justifiés sans SDK ni secret dans le Worker.
### `pre.006` — HTTP live block polling
Suivre le réseau depuis une borne de run explicite, récupérer les blocs observed et projeter leurs transactions vers le pipeline central avec cadence/bornes/stop déterministes.
### `pre.007` — supervision/source inventory multi-source
Faire coexister plusieurs sources dans le supervisor, avec ownership clair des tasks, stop/fault et source health sans modifier la sémantique Worker globale arbitrairement.
### `pre.008` — convergence et observations multiples
Fermer la déduplication/coalescence cross-source et la conservation de provenances/observations distinctes pour une même identité canonique.
### `pre.009` — conflits, backpressure et fairness
Durcir disagreement cross-source, content conflict, saturation globale/par source, fairness minimale et absence de fanout d'hydration non borné.
### `pre.010` — snapshots/health multi-source
Projeter uniquement les dimensions source-neutral réellement utiles : activité, source failures, backpressure et état agrégé sans exposer provider material.
### `pre.011` — races/shutdown hardening
Stop/fault pendant connect/resubscribe/hydration/polling/persistence, sources lentes, tasks orphelines, counters et ordre terminal.
### `pre.012` — completeness/security cross-layer
Canaris Transport -> Worker -> Common RAW -> Store, inventaires API/dépendances, redaction, Legacy/V0/V1 et non-régression Yellowstone.
### `pre.013` — gate technique/live
Workspace complet, Clippy strict, suites ciblées, graphes/duplicates et smokes réellement accessibles. Aucun nouveau scope.
### `pre.014` — réconciliation documentaire
README/USAGE, plan, validation et architectures/références réellement affectées. Pas de CHANGELOG/ROADMAP/prompt suivant.
### `pre.015` — préparation publication
Prompt `0.3.14`, CHANGELOG, ROADMAP et fichiers mécaniques uniquement.
### `rel.001`
Publication stable mécanique après gate validé.
Le forecast peut être allongé par des fixes/tranches dédiées ; il ne doit jamais être compressé en mélangeant les responsabilités de fermeture.
## 11. Versionnement, deltas, commits et tags
Règles obligatoires :
```text
livraison prerelease : 0.3.13-pre.NNN
Cargo : 0.3.13-pre.N
fix : 0.3.13-pre.N.fix.M
delta : deltas/0.3.13/pre.NNN.md ou pre.NNN-fix.NNN.md
commit : v0.3.13-pre.NNN[-fix.NNN]
tag prerelease : aucun
tag stable final : v0.3.13 seulement après rel.001 validée
```
Toute prerelease non-fix synchronise `workspace.package.version`, même documentaire. Une correction strictement doc-only portée par un fix ne bump pas la version Cargo racine. Tout fichier modifié incrémente son header de version selon les règles du dépôt.
Les archives d'échange restent des deltas minimaux.
## 12. Procédure d'application et validation opérateur
Après application d'un delta technique :
```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
```
Puis exécuter les tests ciblés de la tranche. Si un gate devient rouge, produire un fix strictement rattaché à la responsabilité fautive avant d'avancer.
Aucune commande non exécutée ne peut être déclarée PASS.
## 13. Validations Rust / Transport / Config / live / graphes pertinentes
Selon les couches modifiées :
```bash
cargo test -p ksp-worker-raw-transaction-ingest-lib
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
```
Si Config/profils Helius sont modifiés :
```bash
cargo test -p ksp-config-lib
```
Graphes à maintenir/auditer :
```bash
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
```
À la fermeture technique :
```bash
cargo test --workspace --all-targets --all-features
```
Smokes live possibles, uniquement après audit `pre.001` et lorsqu'ils sont réellement configurés :
```text
Solana HTTP Devnet
Solana WS Devnet
Helius transactionSubscribe si credential/tier disponible
provider WS/blockSubscribe réellement accessible
HTTP polling live sur réseau de test approprié
```
Un smoke indisponible est `NON EXÉCUTÉ`, pas PASS. Aucun secret n'est écrit dans une commande persistée, un log, un delta ou un fichier source.
## 14. Critères de clôture de `0.3.13`
La release peut se fermer lorsque :
```text
base Yellowstone 0.3.12 non régressée
runtime resources supporte les sources retenues de manière bornée
logsSubscribe + hydration productif
blockSubscribe productif sur les versions réellement qualifiées
Helius transactionSubscribe productif si retenu/accessible par pre.001
HTTP live polling productif et run-local
sources simultanées convergent vers la même admission/persistence
observations multiples conservées sans dupliquer l'identité RawTransaction
content conflict reste explicite
coalescence/hydration fanout/backpressure bornés
aucun second actor/client Transport dans le Worker
aucun Worker -> Config/Job/backend physique
retry/reconnect restent possédés par la couche appropriée
shutdown/fault multi-source sans tâches orphelines
diagnostics redacted
fixtures/canaris cross-layer verts
smokes accessibles exécutés ou impossibilité qualifiée
gates workspace/clippy/tests/trees verts
documentation réconciliée
prompt 0.3.14 produit dans la dernière prerelease
```
## 15. Release suivante envisagée
`0.3.14` doit reprendre uniquement après publication stable de `0.3.13` et fermer le **gap repair/hardening multi-source** :
```text
replay natif lorsqu'il est réellement adressable
source redondante comme preuve/couverture de gap
scan HTTP blocs pour réparation
hydration des références manquantes
unresolved gaps explicitement observables
politiques degraded/unhealthy/faulted
shutdown pendant repair
smokes provider accessibles
EARLY uniquement si réellement prouvé et encore justifié
```
`0.3.14` ne doit toujours pas transformer le Worker en moteur de campagne historique paramétrée ; le Backfill reste indépendant.
## 16. Instruction d'ouverture
Au début de la prochaine session :
1. vérifier que la base correspond exactement au tag stable `v0.3.12` et que `deltas/0.3.12/rel.001.md` est présent ;
2. lire les règles, le handoff `0.3.12`, l'architecture RAW acquisition et les sources Worker/Transport/Common/Store/Config avant toute modification ;
3. réauditer les surfaces Solana WS, Helius et HTTP actuelles ainsi que les tiers/versions réellement concernés ;
4. produire `0.3.13-pre.001` avec matrice source/material/hydration, architecture multi-source, threat model, sizing, plan `034` et validation `030` ;
5. **ne pas généraliser runtime resources, ne pas brancher `logsSubscribe`/`blockSubscribe`/Helius, ne pas ajouter de polling et ne pas modifier Config avant fermeture de ce gate** ;
6. rescinder `0.3.13` immédiatement si le périmètre réel n'est pas clôturable dans une seule session.