From a4061ee1e143ebb10c22ba5180d412d3c9e8f2e0 Mon Sep 17 00:00:00 2001 From: SinuS Von SifriduS Date: Tue, 8 Sep 2026 10:11:07 +0200 Subject: [PATCH] v0.3.11-pre.004 --- Cargo.toml | 4 +- .../src/error.rs | 11 +- .../src/lib.rs | 20 ++- .../src/runtime.rs | 157 ++++++++++++++++++ .../tests/dependency_boundary.rs | 32 ++-- .../tests/public_api.rs | 21 ++- .../unit_tests/runtime.rs | 127 ++++++++++++++ deltas/0.3.11/pre.004.md | 135 +++++++++++++++ ...ANSACTION_INGEST_WORKER_FOUNDATION_PLAN.md | 10 +- ...AW_TRANSACTION_INGEST_WORKER_FOUNDATION.md | 108 +++++++++++- 10 files changed, 602 insertions(+), 23 deletions(-) create mode 100644 crates/ksp-worker-raw-transaction-ingest-lib/src/runtime.rs create mode 100644 crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime.rs create mode 100644 deltas/0.3.11/pre.004.md diff --git a/Cargo.toml b/Cargo.toml index 871c707..82ab955 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ # file: Cargo.toml -# version: 508 +# version: 509 [workspace] resolver = "3" members = ["crates/ksp-app-backfill-desk", "crates/ksp-app-config-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-store-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-job-api", "crates/ksp-job-backfill-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-raw-transaction-lib", "crates/ksp-store-api", "crates/ksp-store-lib", "crates/ksp-store-postgres-lib", "crates/ksp-wallet-lib", "crates/ksp-worker-api", "crates/ksp-worker-raw-transaction-ingest-lib"] [workspace.package] -version = "0.3.11-pre.3.fix.1" +version = "0.3.11-pre.4" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" diff --git a/crates/ksp-worker-raw-transaction-ingest-lib/src/error.rs b/crates/ksp-worker-raw-transaction-ingest-lib/src/error.rs index 93a4cf2..af18613 100644 --- a/crates/ksp-worker-raw-transaction-ingest-lib/src/error.rs +++ b/crates/ksp-worker-raw-transaction-ingest-lib/src/error.rs @@ -1,10 +1,19 @@ // file: crates/ksp-worker-raw-transaction-ingest-lib/src/error.rs -// version: 2 +// version: 3 +/// Error code used when the RAW transaction ingest Worker reaches an invalid runtime or lifecycle condition. +pub const ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID: ksp_core_lib::ErrorCode = + ksp_core_lib::ErrorCode::new("worker_raw_transaction_ingest", "runtime_invalid"); /// Error code used when RAW transaction ingest Worker settings violate one bounded runtime invariant. pub const ERROR_CODE_RAW_TRANSACTION_INGEST_SETTINGS_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("worker_raw_transaction_ingest", "settings_invalid"); +/// Creates one runtime-domain error carrying only one stable internal condition code. +pub(crate) fn runtime_error(condition: &'static str) -> ksp_core_lib::Error { + return ksp_core_lib::Error::new(crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID, "invalid RAW transaction ingest Worker runtime state") + .with_context("condition", condition); +} + /// Creates one settings-domain error carrying only the stable invalid field name. pub(crate) fn settings_error(field: &'static str) -> ksp_core_lib::Error { return ksp_core_lib::Error::new(crate::ERROR_CODE_RAW_TRANSACTION_INGEST_SETTINGS_INVALID, "invalid RAW transaction ingest Worker settings") diff --git a/crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs b/crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs index 0f3f5b5..974507d 100644 --- a/crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs +++ b/crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs @@ -1,5 +1,5 @@ // file: crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs -// version: 2 +// version: 3 #![warn(missing_docs)] #![deny(unreachable_pub)] @@ -7,18 +7,28 @@ //! Source-neutral runtime foundation for continuous KSP RAW transaction ingestion. //! -//! This tranche owns only the concrete Worker family identity and validated technical -//! settings. Lifecycle, tasks, admission, persistence and snapshots are introduced only -//! by their dedicated prereleases; no live source or Transport dependency exists here. +//! This tranche owns the concrete Worker family identity, validated technical settings +//! and the minimal caller-runtime-owned start/stop lifecycle. Task supervision, bounded +//! admission, persistence and latest-value snapshots remain in their dedicated prereleases; +//! no live source or Transport dependency exists here. mod error; mod identity; +mod runtime; mod settings; +/// Error code used when the RAW transaction ingest Worker reaches an invalid runtime or lifecycle condition. +pub use self::error::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID; /// Error code used when RAW transaction ingest Worker settings violate one bounded runtime invariant. pub use self::error::ERROR_CODE_RAW_TRANSACTION_INGEST_SETTINGS_INVALID; /// Stable Worker kind code used by the continuous RAW transaction ingest vertical. pub use self::identity::RAW_TRANSACTION_INGEST_WORKER_KIND_CODE; +/// Cloneable external control handle for one continuous RAW transaction ingest Worker. +pub use self::runtime::RawTransactionIngestHandle; +/// Runtime-neutral boxed future resolving after one RAW transaction ingest Worker has fully reached a terminal lifecycle state. +pub use self::runtime::RawTransactionIngestTerminalFuture; +/// Entry point owning synchronous validation and task launch for one RAW transaction ingest Worker run. +pub use self::runtime::RawTransactionIngestWorker; /// Default bounded admission queue capacity for one RAW transaction ingest Worker. pub use self::settings::DEFAULT_RAW_TRANSACTION_INGEST_ADMISSION_QUEUE_CAPACITY; /// Default number of concurrent Store persistence operations for one RAW transaction ingest Worker. @@ -40,5 +50,7 @@ pub use self::settings::MIN_RAW_TRANSACTION_INGEST_SHUTDOWN_DRAIN_TIMEOUT; /// Validated source-neutral runtime settings for one continuous RAW transaction ingest Worker. pub use self::settings::RawTransactionIngestSettings; +/// Creates one runtime-domain error without copying runtime/provider/Store values into diagnostics. +pub(crate) use self::error::runtime_error; /// Creates one settings-domain error without copying caller-supplied values into diagnostics. pub(crate) use self::error::settings_error; diff --git a/crates/ksp-worker-raw-transaction-ingest-lib/src/runtime.rs b/crates/ksp-worker-raw-transaction-ingest-lib/src/runtime.rs new file mode 100644 index 0000000..dce5ccd --- /dev/null +++ b/crates/ksp-worker-raw-transaction-ingest-lib/src/runtime.rs @@ -0,0 +1,157 @@ +// file: crates/ksp-worker-raw-transaction-ingest-lib/src/runtime.rs +// version: 1 + +/// Runtime-neutral boxed future resolving after one RAW transaction ingest Worker has fully reached a terminal lifecycle state. +pub type RawTransactionIngestTerminalFuture<'a> = + std::pin::Pin> + std::marker::Send + 'a>>; + +/// Cloneable external control handle for one continuous RAW transaction ingest Worker. +#[derive(Clone)] +pub struct RawTransactionIngestHandle { + stop_sender: tokio::sync::watch::Sender, + stop_token: ksp_worker_api::WorkerStopToken, + terminal_receiver: tokio::sync::watch::Receiver, +} + +impl crate::RawTransactionIngestHandle { + /// Requests cooperative stop and returns `true` only for the first request accepted by the live runtime. + #[must_use] + pub fn request_stop(&self) -> bool { + if !self.stop_token.request_stop() { + return false; + } + return self.stop_sender.send(true).is_ok(); + } + + /// Waits until the private runtime task has published and closed one terminal lifecycle state. + #[must_use] + pub fn wait_terminal(&self) -> crate::RawTransactionIngestTerminalFuture<'_> { + let mut receiver = self.terminal_receiver.clone(); + return std::boxed::Box::pin(async move { + loop { + let current = *receiver.borrow(); + if current.is_terminal() { + let changed = receiver.changed().await; + if changed.is_err() { + return std::result::Result::Ok(current); + } + return std::result::Result::Err(crate::runtime_error("terminal.changed_after_terminal")); + } + let changed = receiver.changed().await; + if changed.is_err() { + return std::result::Result::Err(crate::runtime_error("terminal.closed_before_terminal")); + } + } + }); + } +} + +impl std::fmt::Debug for crate::RawTransactionIngestHandle { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let state = *self.terminal_receiver.borrow(); + return formatter + .debug_struct("RawTransactionIngestHandle") + .field("stop_requested", &self.stop_token.is_stop_requested()) + .field("state", &state) + .finish(); + } +} + +/// Entry point owning synchronous validation and task launch for one RAW transaction ingest Worker run. +pub struct RawTransactionIngestWorker; + +impl crate::RawTransactionIngestWorker { + /// Starts one Worker on the caller-owned current Tokio runtime while retaining the caller-owned Store facade through an `Arc`. + pub fn start( + settings: crate::RawTransactionIngestSettings, + store: std::sync::Arc, + ) -> ksp_core_lib::Result { + 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); + } + return start_foundation(settings, runtime, std::option::Option::Some(store)); + } +} + +fn current_runtime_handle() -> ksp_core_lib::Result { + return match tokio::runtime::Handle::try_current() { + std::result::Result::Ok(value) => std::result::Result::Ok(value), + std::result::Result::Err(_) => std::result::Result::Err(crate::runtime_error("start.runtime_unavailable")), + }; +} + +fn finish_stopped(lifecycle: &mut ksp_worker_api::WorkerLifecycle, sender: &tokio::sync::watch::Sender) { + if lifecycle.mark_stopping().is_err() { + sender.send_replace(ksp_worker_api::WorkerState::Faulted(crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID)); + return; + } + sender.send_replace(lifecycle.state()); + if lifecycle.mark_stopped().is_err() { + sender.send_replace(ksp_worker_api::WorkerState::Faulted(crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID)); + return; + } + sender.send_replace(lifecycle.state()); + return; +} + +async fn run_foundation( + mut lifecycle: ksp_worker_api::WorkerLifecycle, + _store_guard: std::option::Option>, + mut stop_receiver: tokio::sync::watch::Receiver, + terminal_sender: tokio::sync::watch::Sender, +) { + if *stop_receiver.borrow() { + finish_stopped(&mut lifecycle, &terminal_sender); + return; + } + if lifecycle.mark_running().is_err() { + terminal_sender.send_replace(ksp_worker_api::WorkerState::Faulted(crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID)); + return; + } + terminal_sender.send_replace(lifecycle.state()); + loop { + let changed = stop_receiver.changed().await; + if changed.is_err() || *stop_receiver.borrow() { + break; + } + } + finish_stopped(&mut lifecycle, &terminal_sender); + return; +} + +fn start_foundation( + settings: crate::RawTransactionIngestSettings, + runtime: tokio::runtime::Handle, + store_guard: std::option::Option>, +) -> ksp_core_lib::Result { + let kind = match ksp_worker_api::WorkerKindCode::new(crate::RAW_TRANSACTION_INGEST_WORKER_KIND_CODE) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return std::result::Result::Err(crate::runtime_error("start.worker_kind_invalid")), + }; + let mut lifecycle = ksp_worker_api::WorkerLifecycle::new(settings.worker_id().clone(), kind); + if lifecycle.start().is_err() { + return std::result::Result::Err(crate::runtime_error("start.lifecycle_invalid")); + } + let stop_token = ksp_worker_api::WorkerStopToken::new(); + let (stop_sender, stop_receiver) = tokio::sync::watch::channel(false); + let (terminal_sender, terminal_receiver) = tokio::sync::watch::channel(lifecycle.state()); + let handle = crate::RawTransactionIngestHandle { stop_sender, stop_token, terminal_receiver }; + std::mem::drop(runtime.spawn(run_foundation(lifecycle, store_guard, stop_receiver, terminal_sender))); + return std::result::Result::Ok(handle); +} + +fn validate_store_network(settings: &crate::RawTransactionIngestSettings, store_network: &ksp_store_lib::RawNetworkId) -> ksp_core_lib::Result<()> { + if settings.network() != store_network { + return std::result::Result::Err(crate::runtime_error("start.store_network_mismatch")); + } + return std::result::Result::Ok(()); +} + +#[cfg(test)] +#[path = "../unit_tests/runtime.rs"] +mod tests; diff --git a/crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs b/crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs index 71381c7..0b65269 100644 --- a/crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs +++ b/crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs @@ -1,5 +1,5 @@ // file: crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs -// version: 2 +// version: 3 //! Dependency firewall canaries for the RAW transaction ingest Worker foundation. @@ -52,20 +52,30 @@ fn pre_002_manifest_keeps_forbidden_layers_and_live_sources_out() { } #[test] -fn pre_003_source_surface_opens_identity_and_settings_without_runtime_behavior() { +fn pre_004_source_surface_opens_lifecycle_without_supervisor_admission_or_live_sources() { let root = include_str!("../src/lib.rs"); + let runtime = include_str!("../src/runtime.rs"); for required in [ - "mod error;", - "mod identity;", - "mod settings;", - "pub use self::error::ERROR_CODE_RAW_TRANSACTION_INGEST_SETTINGS_INVALID;", - "pub use self::identity::RAW_TRANSACTION_INGEST_WORKER_KIND_CODE;", - "pub use self::settings::RawTransactionIngestSettings;", + "mod runtime;", + "pub use self::runtime::RawTransactionIngestHandle;", + "pub use self::runtime::RawTransactionIngestTerminalFuture;", + "pub use self::runtime::RawTransactionIngestWorker;", + "pub fn start(", + "pub fn request_stop(&self) -> bool", + "pub fn wait_terminal(&self)", ] { - assert!(root.contains(required), "required pre.003 crate-root contract missing: {required}"); + assert!(root.contains(required) || runtime.contains(required), "required pre.004 runtime contract missing: {required}"); } - for forbidden in ["pub mod ", "tokio::", "start(", "spawn(", "JoinHandle", "JoinSet", "mpsc::", "watch::", "ksp_onchain_transport_lib::"] { - assert!(!root.contains(forbidden), "pre.003 opened runtime behavior too early: {forbidden}"); + for forbidden in [ + "JoinSet", + "mpsc::", + "canonicalize_raw_transaction", + "persist_raw_transaction", + "ksp_onchain_transport_lib::", + "RawTransactionIngestSnapshot", + "WorkerSnapshotSource", + ] { + assert!(!root.contains(forbidden) && !runtime.contains(forbidden), "pre.004 opened later runtime scope too early: {forbidden}"); } return; } diff --git a/crates/ksp-worker-raw-transaction-ingest-lib/tests/public_api.rs b/crates/ksp-worker-raw-transaction-ingest-lib/tests/public_api.rs index 36264e2..b92f8f0 100644 --- a/crates/ksp-worker-raw-transaction-ingest-lib/tests/public_api.rs +++ b/crates/ksp-worker-raw-transaction-ingest-lib/tests/public_api.rs @@ -1,5 +1,5 @@ // file: crates/ksp-worker-raw-transaction-ingest-lib/tests/public_api.rs -// version: 4 +// version: 5 //! External public-surface proofs for the RAW transaction ingest Worker identity and settings foundation. @@ -72,3 +72,22 @@ fn pre_003_runtime_bound_errors_are_stable_and_do_not_echo_values() { assert!(!std::format!("{error:?}").contains("raw-ingest-mainnet-001")); return; } + +#[test] +fn pre_004_start_handle_and_terminal_future_are_consumable_without_public_join_handle() { + let _start: fn( + ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSettings, + std::sync::Arc, + ) -> ksp_core_lib::Result = + ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestWorker::start; + let _request_stop: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHandle) -> bool = + ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHandle::request_stop; + let _wait_terminal: for<'a> fn( + &'a ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHandle, + ) -> ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestTerminalFuture<'a> = + ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHandle::wait_terminal; + let root = include_str!("../src/lib.rs"); + assert!(!root.contains("JoinHandle")); + assert!(!root.contains("JoinSet")); + return; +} diff --git a/crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime.rs b/crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime.rs new file mode 100644 index 0000000..803594d --- /dev/null +++ b/crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime.rs @@ -0,0 +1,127 @@ +// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime.rs +// version: 1 + +fn settings(network: &str) -> std::option::Option { + let network = match ksp_store_lib::RawNetworkId::new(network) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return std::option::Option::None, + }; + let worker_id = match ksp_worker_api::WorkerId::new("raw-ingest-runtime-001") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return std::option::Option::None, + }; + return std::option::Option::Some(crate::RawTransactionIngestSettings::with_defaults(network, worker_id)); +} + +#[test] +fn pre_004_current_runtime_is_required_before_spawn() { + let result = super::current_runtime_handle(); + assert!(result.is_err()); + let error = match result { + std::result::Result::Ok(_) => return, + std::result::Result::Err(value) => value, + }; + assert_eq!(error.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID); + assert_eq!(error.context().len(), 1); + assert_eq!(error.context()[0].value(), "start.runtime_unavailable"); + return; +} + +#[test] +fn pre_004_store_network_mismatch_is_rejected_without_echoing_network_values() { + let settings = match settings("mainnet") { + std::option::Option::Some(value) => value, + std::option::Option::None => return, + }; + let other = match ksp_store_lib::RawNetworkId::new("devnet") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + let result = super::validate_store_network(&settings, &other); + assert!(result.is_err()); + let error = match result { + std::result::Result::Ok(_) => return, + std::result::Result::Err(value) => value, + }; + assert_eq!(error.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID); + let rendered = std::format!("{error:?}"); + assert!(!rendered.contains("mainnet")); + assert!(!rendered.contains("devnet")); + return; +} + +#[tokio::test(flavor = "current_thread")] +async fn pre_004_immediate_stop_is_idempotent_and_reaches_stopped_terminal() { + let settings = match settings("mainnet") { + std::option::Option::Some(value) => value, + std::option::Option::None => return, + }; + let handle = match super::start_foundation(settings, tokio::runtime::Handle::current(), std::option::Option::None) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + assert_eq!(*handle.terminal_receiver.borrow(), ksp_worker_api::WorkerState::Starting); + assert!(handle.request_stop()); + assert!(!handle.request_stop()); + let terminal = handle.wait_terminal().await; + assert!(terminal.is_ok()); + let terminal = match terminal { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + assert_eq!(terminal, ksp_worker_api::WorkerState::Stopped); + assert!(terminal.is_terminal()); + return; +} + +#[tokio::test(flavor = "current_thread")] +async fn pre_004_running_lifecycle_stops_through_cloned_handle() { + let settings = match settings("mainnet") { + std::option::Option::Some(value) => value, + std::option::Option::None => return, + }; + let handle = match super::start_foundation(settings, tokio::runtime::Handle::current(), std::option::Option::None) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + tokio::task::yield_now().await; + assert_eq!(*handle.terminal_receiver.borrow(), ksp_worker_api::WorkerState::Running); + let clone = handle.clone(); + assert!(clone.request_stop()); + assert!(!handle.request_stop()); + let terminal = handle.wait_terminal().await; + let terminal = match terminal { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + assert_eq!(terminal, ksp_worker_api::WorkerState::Stopped); + return; +} + +#[tokio::test(flavor = "current_thread")] +async fn pre_004_dropping_last_control_handle_causes_private_runtime_exit() { + let settings = match settings("mainnet") { + std::option::Option::Some(value) => value, + std::option::Option::None => return, + }; + let handle = match super::start_foundation(settings, tokio::runtime::Handle::current(), std::option::Option::None) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + let mut observer = handle.terminal_receiver.clone(); + std::mem::drop(handle); + loop { + let current = *observer.borrow(); + if current.is_terminal() { + let closed = observer.changed().await; + assert!(closed.is_err()); + assert_eq!(current, ksp_worker_api::WorkerState::Stopped); + return; + } + let changed = observer.changed().await; + assert!(changed.is_ok(), "runtime closed before publishing terminal state"); + if changed.is_err() { + return; + } + } +} diff --git a/deltas/0.3.11/pre.004.md b/deltas/0.3.11/pre.004.md new file mode 100644 index 0000000..111985c --- /dev/null +++ b/deltas/0.3.11/pre.004.md @@ -0,0 +1,135 @@ + + + +# Delta `0.3.11-pre.004` — start, handle, lifecycle et terminal Worker + +## Base requise + +```text +0.3.11-pre.003-fix.001 +workspace.package.version = 0.3.11-pre.3.fix.1 +``` + +Le gate opérateur du 8 septembre 2026 est vert après le fix `pre.003-fix.001` sur `fmt`, audits Rust/Markdown, `cargo check --workspace`, Clippy strict et les dix tests de la crate Worker. + +## Objectif + +Matérialiser uniquement la responsabilité `pre.004` du plan `032` : start synchrone sur runtime Tokio caller-owned, handle clonable, stop idempotent, lifecycle générique `ksp-worker-api` et future terminal boxed, sans ouvrir le supervisor de tâches enfants, l'admission, la canonicalisation, la persistence, les snapshots concrets ou une source live. + +## Version + +```text +identifiant de livraison : 0.3.11-pre.004 +workspace.package.version : 0.3.11-pre.4 +``` + +## Contrat public ajouté + +```text +ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID +RawTransactionIngestWorker +RawTransactionIngestHandle +RawTransactionIngestTerminalFuture +RawTransactionIngestWorker::start(settings, Arc) +RawTransactionIngestHandle::request_stop() +RawTransactionIngestHandle::wait_terminal() +``` + +`start` exige un runtime Tokio courant, vérifie le réseau du Store avant spawn et conserve le Store par `Arc` sans le fermer ni l'utiliser encore pour une écriture. + +`request_stop()` partage `WorkerStopToken` et retourne `true` uniquement lors de la première demande acceptée. Le task racine est réveillé par un `watch` privé ; la disparition de tous les handles ferme ce channel et provoque également une terminaison coopérative. + +`wait_terminal()` retourne une future boxed `Send`. Elle attend un `WorkerState` terminal puis la fermeture du sender d'état par le task racine ; aucun `JoinHandle` public n'est nécessaire. + +## Lifecycle de fondation + +```text +Starting -> Running -> Stopping -> Stopped +Starting -> Stopping -> Stopped si stop immédiat +``` + +Les transitions utilisent `WorkerLifecycle`. Un invariant impossible devient `Faulted(worker_raw_transaction_ingest.runtime_invalid)` sans payload, worker id, réseau ou erreur externe dans le diagnostic. + +## Hors scope conservé + +```text +JoinSet / tasks enfants +mpsc / admission +canonicalisation common RAW +observation key +persistence Store +snapshots concrets / WorkerSnapshotSource +source live / Transport +Config +``` + +Les méthodes de snapshot prévues par la surface finale restent différées à `pre.008`. `pre.005` introduit le supervisor et l'ownership des tâches enfants autour du wake-up de stop déjà minimalement nécessaire ici. + +## Tests ajoutés ou étendus + +Les tests couvrent statiquement ou via harness runtime privé : + +```text +runtime Tokio courant obligatoire +network Store mismatch sûr +stop immédiat idempotent +Running -> stop via clone de handle +drop du dernier handle -> sortie du task racine +surface publique start/handle/terminal future +absence de JoinHandle public et du scope pre.005+ +``` + +## Fichiers ajoutés + +```text +crates/ksp-worker-raw-transaction-ingest-lib/src/runtime.rs +crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime.rs +deltas/0.3.11/pre.004.md +``` + +## Fichiers modifiés + +```text +Cargo.toml +crates/ksp-worker-raw-transaction-ingest-lib/src/error.rs +crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs +crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs +crates/ksp-worker-raw-transaction-ingest-lib/tests/public_api.rs +docs/plans/032-V0_3_11_RAW_TRANSACTION_INGEST_WORKER_FOUNDATION_PLAN.md +docs/validation/028-V0_3_11_RAW_TRANSACTION_INGEST_WORKER_FOUNDATION.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 +scan statique des interdits/scope runtime +``` + +L'environnement d'assemblage ne fournit ni `cargo`, ni `rustc`, ni `rustfmt`. Aucun gate Cargo de `pre.004` n'est déclaré PASS localement. + +## Gate opérateur demandé + +```bash +cargo fmt --all +python3 scripts/audit_rust_workspace_rules.py +python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas +cargo check --workspace +cargo clippy --workspace --all-targets --all-features -- -D warnings +cargo test -p ksp-worker-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 +``` + +## Décision + +`pre.005` reste bloquée jusqu'à validation opérateur verte de cette tranche. + +## Questions ouvertes + +Aucune nouvelle question architecturale. Le supervisor/tasks enfants, l'admission, la persistence et les snapshots restent dans leurs tranches planifiées. diff --git a/docs/plans/032-V0_3_11_RAW_TRANSACTION_INGEST_WORKER_FOUNDATION_PLAN.md b/docs/plans/032-V0_3_11_RAW_TRANSACTION_INGEST_WORKER_FOUNDATION_PLAN.md index ec97bc3..b4ab63d 100644 --- a/docs/plans/032-V0_3_11_RAW_TRANSACTION_INGEST_WORKER_FOUNDATION_PLAN.md +++ b/docs/plans/032-V0_3_11_RAW_TRANSACTION_INGEST_WORKER_FOUNDATION_PLAN.md @@ -1,5 +1,5 @@ - + # Plan v0.3.11 — fondation runtime du Worker RawTransaction ingest @@ -705,15 +705,19 @@ Budget cible : **10–15 min**. Créer la crate minimale, l'ajouter au workspace Budget cible : **10–15 min**. Kind code, settings, defaults/bounds, validation network/worker identity et erreurs correspondantes. Aucun spawn. -État après matérialisation : **implémenté, gate opérateur requis**. Le kind stable vaut `raw_transaction_ingest`; les settings portent uniquement `network`, `worker_id`, queue, concurrence de persistence et drain timeout. Les identités restent validées par leurs types propriétaires, tandis que les trois bornes techniques sont verrouillées par un code d'erreur Worker stable et des tests unitaires/externes. Aucun `start`, spawn, channel, supervisor ou appel Store n'est introduit. +État après matérialisation : **implémenté et gate opérateur validé après `pre.003-fix.001`**. Le kind stable vaut `raw_transaction_ingest`; les settings portent uniquement `network`, `worker_id`, queue, concurrence de persistence et drain timeout. Les identités restent validées par leurs types propriétaires, tandis que les trois bornes techniques sont verrouillées par un code d'erreur Worker stable et des tests unitaires/externes. Le fix `pre.003-fix.001` remplace les deux propagations `?` interdites par un contrôle de flux explicite ; le gate communiqué le 8 septembre 2026 est ensuite vert sur `fmt`, audits, `check`, Clippy strict et les dix tests de la crate. ### `pre.004` — start/handle/lifecycle/terminal Budget cible : **15–20 min**. Start sur runtime caller-owned, handle public, stop idempotent, lifecycle générique, terminal future sans JoinHandle public. Harness runtime minimal. +État après matérialisation : **implémenté, gate opérateur requis**. `RawTransactionIngestWorker::start(settings, Arc)` vérifie le runtime Tokio courant puis le réseau Store avant spawn. Le handle clonable expose uniquement `request_stop()` et `wait_terminal()` ; un `watch` privé sert au wake-up de stop et un `watch` privé au terminal de fondation. Le task racine ne possède encore aucun task enfant, aucune admission/persistence et aucun snapshot concret ; la disparition de tous les handles ferme le sender de stop et fait terminer proprement ce task. `wait_terminal()` attend le terminal puis la fermeture du sender d'état, sans exposer de `JoinHandle`. + +Les méthodes `snapshot_source()` et `worker_snapshot_source()` prévues par la surface finale restent volontairement différées à `pre.008`, où leur contrat latest-value concret sera effectivement prouvé. + ### `pre.005` — supervisor privé + ownership des tâches -Budget cible : **15–20 min**. Supervisor, JoinSet/joins privés, stop wake-up, test source seam et preuve qu'aucune tâche ne survit au terminal. Pas encore de persistence réelle. +Budget cible : **15–20 min**. Introduire le supervisor, les JoinSet/joins privés, intégrer le wake-up de stop déjà matérialisé à l'ownership des tâches enfants, ajouter le test source seam et prouver qu'aucune tâche enfant ne survit au terminal. Pas encore de persistence réelle. ### `pre.006` — admission bornée + canonicalisation common diff --git a/docs/validation/028-V0_3_11_RAW_TRANSACTION_INGEST_WORKER_FOUNDATION.md b/docs/validation/028-V0_3_11_RAW_TRANSACTION_INGEST_WORKER_FOUNDATION.md index 6a53d38..992dd9a 100644 --- a/docs/validation/028-V0_3_11_RAW_TRANSACTION_INGEST_WORKER_FOUNDATION.md +++ b/docs/validation/028-V0_3_11_RAW_TRANSACTION_INGEST_WORKER_FOUNDATION.md @@ -1,5 +1,5 @@ - + # Validation v0.3.11 — fondation runtime du Worker RawTransaction ingest @@ -634,3 +634,109 @@ cargo tree -p ksp-worker-raw-transaction-ingest-lib -e features ``` Critère de passage : defaults/bornes/erreurs/identités sont verts, le dependency firewall reste inchangé et aucun comportement runtime de `pre.004` n'apparaît. + +## 15. Fermeture opérateur `pre.003-fix.001` et matérialisation `pre.004` + +### 15.1 Fermeture opérateur de `pre.003-fix.001` + +Le journal opérateur communiqué le 8 septembre 2026 ferme le correctif technique `0.3.11-pre.003-fix.001` (`workspace.package.version = 0.3.11-pre.3.fix.1`) : + +```text +cargo fmt --all : terminé sans erreur +python3 scripts/audit_rust_workspace_rules.py : clean, export completeness 0 +python3 scripts/audit_markdown_tables.py ... : clean (340 tables, 778 files) +cargo check --workspace : terminé sans erreur +cargo clippy --workspace --all-targets --all-features -- -D warnings : terminé sans erreur +cargo test -p ksp-worker-raw-transaction-ingest-lib : 4 unit + 3 dependency-boundary + 3 public API PASS, 0 échec +doc-tests : PASS +``` + +Le défaut `clippy::question_mark_used` de `pre.003` est donc clos. `pre.004` peut être ouverte. + +### 15.2 Scope matérialisé dans `pre.004` + +La tranche ajoute : + +```text +ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID +RawTransactionIngestWorker +RawTransactionIngestHandle +RawTransactionIngestTerminalFuture +RawTransactionIngestWorker::start(settings, Arc) +RawTransactionIngestHandle::request_stop() +RawTransactionIngestHandle::wait_terminal() +``` + +`start` reste synchrone et ne crée aucun runtime. Il exige un `tokio::runtime::Handle::try_current()` valide avant le spawn, puis compare `settings.network()` au réseau sûr exposé par `Store::runtime_snapshot()`. Une incompatibilité retourne uniquement le code stable `worker_raw_transaction_ingest.runtime_invalid` avec un contexte statique ; aucun identifiant de Worker ou valeur réseau n'est recopié dans l'erreur. + +Le `Store` est reçu sous `Arc` et seulement retenu par le task racine de fondation ; aucune opération Store n'est encore appelée et le Worker ne ferme jamais la façade. + +### 15.3 Lifecycle et terminal + +La state machine concrète réutilise exclusivement `ksp-worker-api::WorkerLifecycle` : + +```text +Created -> Starting -> Running -> Stopping -> Stopped +Created -> Starting -> Stopping -> Stopped (stop immédiat) +``` + +Tout invariant de transition impossible est projeté vers `Faulted(ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID)` sans texte externe. + +Le handle partage `WorkerStopToken` pour l'idempotence. Le premier `request_stop()` accepté réveille le task racine via un `tokio::sync::watch` privé ; les appels suivants retournent `false`. La fermeture de tous les senders de stop, notamment lorsque tous les handles sont droppés sans appel explicite, est interprétée comme une terminaison coopérative afin de ne pas laisser le task racine attendre indéfiniment. + +Le terminal de fondation est porté par un `watch` privé. `wait_terminal()` retourne une future boxed `Send` appartenant à la crate concrète et attend à la fois un état terminal puis la fermeture du sender par le task racine. Aucun `tokio::task::JoinHandle` ni `JoinSet` n'est exposé publiquement. + +### 15.4 Frontière volontaire de `pre.004` + +La tranche n'introduit encore aucun : + +```text +JoinSet / ownership de tâches enfants +mpsc d'admission +PrivateRawTransactionIngress +canonicalisation common RAW +observation key +appel Store de persistence +RawTransactionIngestSnapshot +WorkerSnapshotSource concret +source live +Transport +Config +``` + +Le `watch` de stop est le wake-up minimal nécessaire au contrat start/stop. `pre.005` l'intègre au supervisor et à l'ownership des futures tâches enfants ; `pre.008` ouvre seulement alors les deux sources de snapshot prévues par la surface finale. + +### 15.5 Preuves locales d'assemblage `pre.004` + +Exécuté 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), 779 file(s)) + +scan statique : aucun ?, unwrap, expect ou panic de production dans la crate Worker +scan statique : aucun JoinSet, mpsc, persistence, canonicalisation ou Transport dans runtime.rs +``` + +L'environnement d'assemblage ne fournit ni `cargo`, ni `rustc`, ni `rustfmt`. Aucun gate Cargo `pre.004` n'est déclaré PASS localement. + +### 15.6 Gate opérateur demandé pour `pre.004` + +```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 +``` + +Critère de passage : le start caller-owned, le contrôle de réseau, les transitions generic Worker API, le stop idempotent et le terminal boxed sont verts ; aucun `JoinHandle` public, supervisor enfant, admission, Store write, snapshot concret ou source live n'apparaît. +