v0.3.15-pre.007

This commit is contained in:
2026-09-13 17:36:06 +02:00
parent 487bcbf0c1
commit 0d9ec5c490
20 changed files with 1071 additions and 109 deletions

View File

@@ -1,12 +1,12 @@
# file: Cargo.toml
# version: 605
# version: 606
[workspace]
resolver = "3"
members = ["crates/ksp-app-backfill-desk", "crates/ksp-app-config-desk", "crates/ksp-app-raw-transaction-ingest-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.15-pre.6.fix.3"
version = "0.3.15-pre.7"
edition = "2024"
license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"

View File

@@ -1,5 +1,5 @@
# file: crates/ksp-app-raw-transaction-ingest-desk/Cargo.toml
# version: 2
# version: 3
[package]
name = "ksp-app-raw-transaction-ingest-desk"
@@ -28,6 +28,7 @@ ksp-config-lib = { path = "../ksp-config-lib" }
ksp-core-lib = { path = "../ksp-core-lib" }
ksp-logging-lib = { path = "../ksp-logging-lib" }
ksp-onchain-transport-lib = { path = "../ksp-onchain-transport-lib" }
ksp-worker-raw-transaction-ingest-lib = { path = "../ksp-worker-raw-transaction-ingest-lib" }
serde = { workspace = true, features = ["derive"] }
tauri.workspace = true
tauri-plugin-tracing.workspace = true

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-app-raw-transaction-ingest-desk/README.md -->
<!-- version: 5 -->
<!-- version: 6 -->
# `ksp-app-raw-transaction-ingest-desk`
@@ -20,13 +20,15 @@ bin : ksp-app-raw-transaction-ingest-desk
```text
Config -> composite, profils, secrets et résolution effective
Transport -> capabilities HTTP/WS/gRPC et ressources physiques
Desk -> catalogue logique, inventaire Config-only et projections sûres
Desk -> catalogue logique, inventaire Config-only et reconstruction Start sans I/O
Store -> persistance via la façade KSP
Worker -> acquisition continue et lifecycle d'exécution
Worker -> validation des contrats source, acquisition continue et lifecycle d'exécution
```
L'inventaire de routes est calculé côté Rust à partir du composite Config validé et des settings Transport/Store résolus. Il ne lance aucun probe réseau et n'ouvre ni Store ni Worker.
Avant le Start réel, le backend peut revalider une sélection logique par `profile_id + route_id + inventory_generation + commitment`. Cette prévalidation recharge Config, reproouve le réseau Store/Transport, reconstruit le pool HTTP ou l'endpoint WS/gRPC exact requis et passe par le constructeur public de la source `ksp-worker-raw-transaction-ingest-lib`. La ressource ainsi reconstruite est immédiatement détruite : aucun socket n'est ouvert, aucun Store n'est ouvert et aucun Worker n'est lancé dans cette tranche.
Les profils applicatifs sont réseau-centriques (`devnet`, `mainnet`, `testnet`). Un profil peut agréger plusieurs profils Transport du même réseau : le provider reste une source de capability et ne devient jamais une identité réseau ni un choix de profil applicatif.
Une route explicitement liée à un provider nest projetée pour un réseau que si le composite déclare une source Transport de ce provider. Ainsi, la route Helius Transaction existe sur Devnet/Mainnet mais nest pas présentée sur Testnet tant quHelius ne fournit pas de source Testnet configurée.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/app_state.rs
// version: 2
// version: 3
//! Shared backend state owned by the Raw Transaction Ingest Desk Tauri application.
@@ -98,6 +98,24 @@ impl crate::AppState {
return std::result::Result::Ok(inventory);
}
/// Revalidates one future Start request and reconstructs its exact Transport/Worker source contract without launching a Worker.
pub(crate) fn validate_route_start(
&self,
request: &crate::RawIngestRouteStartRequestDto,
) -> ksp_core_lib::Result<crate::RawIngestRouteStartValidationDto> {
let generation = self.inventory_generation.lock();
let generation = match generation {
std::result::Result::Ok(value) => *value,
std::result::Result::Err(_) => {
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_APP_STATE_LOCK_FAILED,
"Raw Transaction Ingest Desk route inventory generation lock is poisoned",
));
},
};
return crate::validate_route_start(&self.config_management, generation, request);
}
/// Builds the safe scaffold status exposed by the shell.
pub(crate) fn shell_status(&self) -> ksp_core_lib::Result<crate::ShellStatusDto> {
let document_count = self.config_management.engine().registry().descriptors().count();
@@ -131,7 +149,7 @@ impl crate::AppState {
application_version: env!("CARGO_PKG_VERSION").to_owned(),
config_document_count: document_count,
fallback_logging_active: runtime.fallback_active,
shell_phase: "pre.006-route-inventory".to_owned(),
shell_phase: "pre.007-start-resource-revalidation".to_owned(),
startup_diagnostic: runtime.startup_diagnostic.clone(),
});
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/constants.rs
// version: 3
// version: 4
//! Application-owned tracing targets and Config component identifiers.
@@ -21,6 +21,8 @@ pub(crate) const TRACING_DOMAIN_BOOTSTRAP: &str = "raw_transaction_ingest.bootst
pub(crate) const TRACING_DOMAIN_FRONTEND: &str = "frontend";
/// Structured domain used while rebuilding Config-only route inventory.
pub(crate) const TRACING_DOMAIN_INVENTORY: &str = "raw_transaction_ingest.inventory";
/// Structured domain used while revalidating future Start requests and reconstructing route resources.
pub(crate) const TRACING_DOMAIN_ROUTE_START: &str = "raw_transaction_ingest.route_start";
/// Structured domain used by the Raw Transaction Ingest Desk shell.
pub(crate) const TRACING_DOMAIN_SHELL: &str = "raw_transaction_ingest.shell";
/// Structured domain used by Tauri window lifecycle operations.

View File

@@ -1,12 +1,12 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/dto_route.rs
// version: 3
// version: 4
//! Safe route-identity, Config-composability and lifecycle DTOs for Raw Transaction Ingest Desk.
use ts_rs::TS; // rust-rules: trait-import
/// Stable logical V1 route identifiers owned by Raw Transaction Ingest Desk.
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, TS)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)]
#[serde(rename_all = "kebab-case")]
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestRouteId.ts")]
pub(crate) enum RawIngestRouteId {
@@ -26,7 +26,13 @@ impl crate::RawIngestRouteId {
/// Returns all V1 route identifiers in stable UI order.
#[must_use]
pub(crate) fn all() -> std::vec::Vec<Self> {
return vec![Self::YellowstoneHydrated, Self::StandardLogsHydrated, Self::StandardBlockDirect, Self::HeliusTransactionHydrated, Self::HttpBlockPolling];
return vec![
Self::YellowstoneHydrated,
Self::StandardLogsHydrated,
Self::StandardBlockDirect,
Self::HeliusTransactionHydrated,
Self::HttpBlockPolling,
];
}
/// Returns the application-owned route family used by safe UI projections.
@@ -52,6 +58,81 @@ impl crate::RawIngestRouteId {
Self::HttpBlockPolling => "HTTP Block Polling",
};
}
/// Returns the stable kebab-case route identifier used by safe logs and IPC.
#[must_use]
pub(crate) const fn as_str(self) -> &'static str {
return match self {
Self::YellowstoneHydrated => "yellowstone-hydrated",
Self::StandardLogsHydrated => "standard-logs-hydrated",
Self::StandardBlockDirect => "standard-block-direct",
Self::HeliusTransactionHydrated => "helius-transaction-hydrated",
Self::HttpBlockPolling => "http-block-polling",
};
}
}
/// Start-time commitments accepted by Raw Transaction Ingest Desk.
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestCommitment.ts")]
pub(crate) enum RawIngestCommitment {
/// Confirmed commitment.
Confirmed,
/// Finalized commitment.
Finalized,
}
impl crate::RawIngestCommitment {
/// Returns the stable wire label.
#[must_use]
pub(crate) const fn as_str(self) -> &'static str {
return match self {
Self::Confirmed => "confirmed",
Self::Finalized => "finalized",
};
}
/// Maps the application-owned bounded commitment into the Transport contract.
#[must_use]
pub(crate) const fn into_transport(self) -> ksp_onchain_transport_lib::SolanaCommitment {
return match self {
Self::Confirmed => ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
Self::Finalized => ksp_onchain_transport_lib::SolanaCommitment::Finalized,
};
}
}
/// Safe request used to revalidate and reconstruct one future route Start without accepting physical resources from the frontend.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestRouteStartRequestDto.ts")]
pub(crate) struct RawIngestRouteStartRequestDto {
/// Confirmed/finalized commitment selected for the future Worker.
pub(crate) commitment: crate::RawIngestCommitment,
/// Inventory generation observed by the caller.
pub(crate) inventory_generation: u32,
/// Safe logical network-profile identifier selected from the current inventory.
pub(crate) profile_id: String,
/// Stable logical route identifier.
pub(crate) route_id: crate::RawIngestRouteId,
}
/// Safe acknowledgement that exact Start-time Transport resources were reconstructed and validated without Worker launch.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestRouteStartValidationDto.ts")]
pub(crate) struct RawIngestRouteStartValidationDto {
/// Commitment validated by the exact Worker source constructor.
pub(crate) commitment: crate::RawIngestCommitment,
/// Inventory generation validated against backend state.
pub(crate) inventory_generation: u32,
/// Logical network re-proven from Store and Transport Config.
pub(crate) network: String,
/// Safe logical network-profile identifier.
pub(crate) profile_id: String,
/// Exact logical route whose Transport resources were reconstructed.
pub(crate) route_id: crate::RawIngestRouteId,
}
/// Stable application-owned route families used only for safe presentation and orchestration.
@@ -182,7 +263,11 @@ impl crate::RawIngestRouteDto {
/// Builds one route that is not composable from the current Config profile.
#[must_use]
pub(crate) fn unavailable(route_id: crate::RawIngestRouteId, network: std::option::Option<&str>, reason: crate::RawIngestRouteUnavailableReason) -> Self {
pub(crate) fn unavailable(
route_id: crate::RawIngestRouteId,
network: std::option::Option<&str>,
reason: crate::RawIngestRouteUnavailableReason,
) -> Self {
return Self {
family: route_id.family(),
label: route_id.label().to_owned(),

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/errors.rs
// version: 2
// version: 3
//! Stable Raw Transaction Ingest Desk application error codes.
@@ -23,6 +23,15 @@ pub(crate) const ERROR_CODE_LOGGING_BOOTSTRAP_FAILED: ksp_core_lib::ErrorCode =
/// Config-only route inventory cannot be projected safely from the validated composite.
pub(crate) const ERROR_CODE_ROUTE_INVENTORY_INVALID: ksp_core_lib::ErrorCode =
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "route_inventory_invalid");
/// Exact Start-time Transport resources cannot be reconstructed safely.
pub(crate) const ERROR_CODE_ROUTE_START_PREPARATION_FAILED: ksp_core_lib::ErrorCode =
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "route_start_preparation_failed");
/// Start request references an inventory generation that is no longer current.
pub(crate) const ERROR_CODE_ROUTE_START_STALE_INVENTORY: ksp_core_lib::ErrorCode =
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "stale_inventory");
/// Start request references a route that is no longer Configured/applicable.
pub(crate) const ERROR_CODE_ROUTE_START_UNAVAILABLE: ksp_core_lib::ErrorCode =
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "route_start_unavailable");
/// Splash readiness was invoked from a window other than the splash window.
pub(crate) const ERROR_CODE_SPLASH_ORIGIN_INVALID: ksp_core_lib::ErrorCode =
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "splash_origin_invalid");
@@ -30,9 +39,11 @@ pub(crate) const ERROR_CODE_SPLASH_ORIGIN_INVALID: ksp_core_lib::ErrorCode =
pub(crate) const ERROR_CODE_SPLASH_SETTING_INVALID: ksp_core_lib::ErrorCode =
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "splash_setting_invalid");
/// Tauri runtime assembly or execution failed.
pub(crate) const ERROR_CODE_TAURI_RUNTIME_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "tauri_runtime_failed");
pub(crate) const ERROR_CODE_TAURI_RUNTIME_FAILED: ksp_core_lib::ErrorCode =
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "tauri_runtime_failed");
/// A required Tauri window is missing from the configured application runtime.
pub(crate) const ERROR_CODE_TAURI_WINDOW_MISSING: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "tauri_window_missing");
pub(crate) const ERROR_CODE_TAURI_WINDOW_MISSING: ksp_core_lib::ErrorCode =
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "tauri_window_missing");
/// A Tauri window show/focus/destroy/event operation failed.
pub(crate) const ERROR_CODE_TAURI_WINDOW_OPERATION_FAILED: ksp_core_lib::ErrorCode =
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "tauri_window_operation_failed");

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/lib.rs
// version: 3
// version: 4
//! Tauri desktop application scaffold for composing and supervising KSP live RAW transaction ingest routes.
@@ -16,6 +16,7 @@ mod errors;
mod frontend_logging;
mod logging_runtime;
mod route_inventory;
mod route_start;
mod splash;
mod tauri;
mod tw_main;
@@ -44,10 +45,10 @@ pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_LOGGING;
pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_STORE;
/// Composite-local identifier for the standard Transport component.
pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_TRANSPORT;
/// Composite-local optional Transport source for Helius WebSocket capabilities.
pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_TRANSPORT_HELIUS;
/// Prefix used by optional same-network Transport capability sources.
pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_TRANSPORT_PREFIX;
/// Composite-local optional Transport source for Helius WebSocket capabilities.
pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_TRANSPORT_HELIUS;
/// Composite-local optional Transport source for Yellowstone gRPC capabilities.
pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_TRANSPORT_YELLOWSTONE;
/// Structured domain used while bootstrapping Config and Logging.
@@ -56,6 +57,8 @@ pub(crate) use self::constants::TRACING_DOMAIN_BOOTSTRAP;
pub(crate) use self::constants::TRACING_DOMAIN_FRONTEND;
/// Structured domain used while rebuilding Config-only route inventory.
pub(crate) use self::constants::TRACING_DOMAIN_INVENTORY;
/// Structured domain used while revalidating future Start requests.
pub(crate) use self::constants::TRACING_DOMAIN_ROUTE_START;
/// Structured domain used by the Raw Transaction Ingest Desk shell.
pub(crate) use self::constants::TRACING_DOMAIN_SHELL;
/// Structured domain used by Tauri window lifecycle operations.
@@ -72,6 +75,8 @@ pub(crate) use self::constants::TRACING_TARGET_FRONTEND_SPLASH;
pub(crate) use self::dto_common::CommandErrorDto;
/// Safe scaffold/runtime snapshot exposed to the shell.
pub(crate) use self::dto_common::ShellStatusDto;
/// Bounded confirmed/finalized commitment accepted by future route Start requests.
pub(crate) use self::dto_route::RawIngestCommitment;
/// Safe route inventory for one logical composite profile.
pub(crate) use self::dto_route::RawIngestProfileInventoryDto;
/// Safe Config-only composability projection for one logical route.
@@ -84,6 +89,10 @@ pub(crate) use self::dto_route::RawIngestRouteFoundationDto;
pub(crate) use self::dto_route::RawIngestRouteId;
/// Complete deterministic Config-only route inventory.
pub(crate) use self::dto_route::RawIngestRouteInventoryDto;
/// Safe future Start request containing only logical route selection and inventory generation.
pub(crate) use self::dto_route::RawIngestRouteStartRequestDto;
/// Safe acknowledgement that exact Start-time resources were reconstructed without Worker launch.
pub(crate) use self::dto_route::RawIngestRouteStartValidationDto;
/// Stable route lifecycle states reserved for runtime projections.
pub(crate) use self::dto_route::RawIngestRouteState;
/// Safe reason explaining why one logical route is not composable from Config.
@@ -102,6 +111,12 @@ pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_TARGET_INVALID;
pub(crate) use self::errors::ERROR_CODE_LOGGING_BOOTSTRAP_FAILED;
/// Config-only route inventory cannot be projected safely.
pub(crate) use self::errors::ERROR_CODE_ROUTE_INVENTORY_INVALID;
/// Exact Start-time Transport resources cannot be reconstructed safely.
pub(crate) use self::errors::ERROR_CODE_ROUTE_START_PREPARATION_FAILED;
/// Start request references a stale inventory generation.
pub(crate) use self::errors::ERROR_CODE_ROUTE_START_STALE_INVENTORY;
/// Start request references a route that is no longer Configured/applicable.
pub(crate) use self::errors::ERROR_CODE_ROUTE_START_UNAVAILABLE;
/// Splash readiness originated from an invalid window.
pub(crate) use self::errors::ERROR_CODE_SPLASH_ORIGIN_INVALID;
/// Splash timing configuration is invalid.
@@ -120,6 +135,10 @@ pub(crate) use self::frontend_logging::emit_frontend_log_event;
pub(crate) use self::logging_runtime::launch_identity;
/// Rebuilds the complete safe route inventory from validated Config without runtime I/O.
pub(crate) use self::route_inventory::build_route_inventory;
/// Rebuilds the route inventory from one already captured Config environment snapshot.
pub(crate) use self::route_inventory::build_route_inventory_with_environment;
/// Revalidates one future Start request and reconstructs its exact Transport-owned Worker source contract without launch.
pub(crate) use self::route_start::validate_route_start;
/// Command emitted by Rust to the splash frontend.
pub(crate) use self::splash::SplashOrderDto;
/// Runtime timings used by the common desk splash lifecycle.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/route_inventory.rs
// version: 3
// version: 4
//! Config-only logical route inventory for Raw Transaction Ingest Desk.
@@ -48,7 +48,10 @@ impl RouteCapabilities {
}
/// Rebuilds the complete safe route inventory from validated Config without opening any network, Store or Worker resource.
pub(crate) fn build_route_inventory(management: &ksp_config_lib::ConfigManagement, generation: u32) -> ksp_core_lib::Result<crate::RawIngestRouteInventoryDto> {
pub(crate) fn build_route_inventory(
management: &ksp_config_lib::ConfigManagement,
generation: u32,
) -> ksp_core_lib::Result<crate::RawIngestRouteInventoryDto> {
let environment = ksp_config_lib::ConfigEnvironment::load();
let environment = match environment {
std::result::Result::Ok(value) => value,
@@ -57,7 +60,8 @@ pub(crate) fn build_route_inventory(management: &ksp_config_lib::ConfigManagemen
return build_route_inventory_with_environment(management, &environment, generation);
}
fn build_route_inventory_with_environment(
/// Rebuilds the complete safe route inventory from one already captured Config environment snapshot.
pub(crate) pub(crate) fn build_route_inventory_with_environment(
management: &ksp_config_lib::ConfigManagement,
environment: &ksp_config_lib::ConfigEnvironment,
generation: u32,
@@ -69,7 +73,12 @@ fn build_route_inventory_with_environment(
};
let mut profiles = std::vec::Vec::with_capacity(catalog.profile_ids.len());
for profile_id in &catalog.profile_ids {
profiles.push(build_profile_inventory(management, environment, profile_id.as_str(), profile_id == &catalog.default_profile));
profiles.push(build_profile_inventory(
management,
environment,
profile_id.as_str(),
profile_id == &catalog.default_profile,
));
}
ksp_logging_lib::debug!(
target: crate::TRACING_TARGET,
@@ -155,8 +164,11 @@ fn build_profile_inventory(
},
};
let network = store.settings().network().as_str().to_owned();
let base_transport_profile =
crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_TRANSPORT, ksp_config_lib::FILE_ID_STD_TRANSPORT);
let base_transport_profile = crate::required_composite_component_profile(
&composite,
crate::COMPOSITE_COMPONENT_ID_TRANSPORT,
ksp_config_lib::FILE_ID_STD_TRANSPORT,
);
let base_transport_profile = match base_transport_profile {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
@@ -172,7 +184,12 @@ fn build_profile_inventory(
let base_transport = match base_transport {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return unavailable_profile(profile_id, is_default, std::option::Option::Some(network.as_str()), resolution_error_reason(&error));
return unavailable_profile(
profile_id,
is_default,
std::option::Option::Some(network.as_str()),
resolution_error_reason(&error),
);
},
};
if !transport_matches_network(&base_transport, network.as_str()) {

View File

@@ -0,0 +1,493 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/route_start.rs
// version: 1
//! Start-time Config revalidation and Transport-resource reconstruction without Worker launch.
/// Revalidates one future Start request against the current inventory generation and reconstructs the exact Worker source contract without starting it.
pub(crate) fn validate_route_start(
management: &ksp_config_lib::ConfigManagement,
current_generation: u32,
request: &crate::RawIngestRouteStartRequestDto,
) -> ksp_core_lib::Result<crate::RawIngestRouteStartValidationDto> {
if current_generation == 0 || request.inventory_generation != current_generation {
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_ROUTE_START_STALE_INVENTORY,
"Raw Transaction Ingest Desk Start request uses a stale route inventory generation",
));
}
let environment = ksp_config_lib::ConfigEnvironment::load();
let environment = match environment {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return route_start_error("Cannot resolve Start-time Config environment", error),
};
let inventory = crate::build_route_inventory_with_environment(management, &environment, current_generation);
let inventory = match inventory {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return route_start_error("Cannot rebuild Start-time route inventory", error),
};
let profile = inventory.profiles.iter().find(|profile| return profile.profile_id == request.profile_id);
let profile = match profile {
std::option::Option::Some(value) => value,
std::option::Option::None => return route_unavailable_error(),
};
let route = profile.routes.iter().find(|route| return route.route_id == request.route_id);
let route = match route {
std::option::Option::Some(value) if value.selectable && value.state == crate::RawIngestRouteState::Configured => value,
std::option::Option::Some(_) | std::option::Option::None => return route_unavailable_error(),
};
let network = match route.network.as_deref() {
std::option::Option::Some(value) => value,
std::option::Option::None => return route_unavailable_error(),
};
let composite = crate::load_raw_transaction_ingest_desk_composite_profile(management, std::option::Option::Some(request.profile_id.as_str()));
let composite = match composite {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return route_start_error("Cannot resolve Start-time composite profile", error),
};
let store_profile = crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_STORE, ksp_config_lib::FILE_ID_STD_STORE);
let store_profile = match store_profile {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return route_start_error("Cannot resolve Start-time Store profile", error),
};
let store = management.engine().resolve_store_config_profile(&store_profile, &environment);
let store = match store {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return route_start_error("Cannot resolve Start-time Store settings", error),
};
if store.settings().network().as_str() != network {
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_ROUTE_START_PREPARATION_FAILED,
"Raw Transaction Ingest Desk Start-time Store network no longer matches the selected route",
));
}
let transport_profile = crate::required_composite_component_profile(
&composite,
crate::COMPOSITE_COMPONENT_ID_TRANSPORT,
ksp_config_lib::FILE_ID_STD_TRANSPORT,
);
let transport_profile = match transport_profile {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return route_start_error("Cannot resolve Start-time base Transport profile", error),
};
let transport = management.engine().resolve_transport_config_profile(&transport_profile, &environment);
let transport = match transport {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return route_start_error("Cannot resolve Start-time base Transport settings", error),
};
if !transport_matches_network(&transport, network) {
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_ROUTE_START_PREPARATION_FAILED,
"Raw Transaction Ingest Desk Start-time Transport network no longer matches the selected route",
));
}
let commitment = request.commitment.into_transport();
let prepared = match request.route_id {
crate::RawIngestRouteId::YellowstoneHydrated => prepare_yellowstone(management, &environment, &composite, &transport, network, commitment),
crate::RawIngestRouteId::StandardLogsHydrated => prepare_standard_logs(&transport, network, commitment),
crate::RawIngestRouteId::StandardBlockDirect => prepare_standard_block(&transport, network, commitment),
crate::RawIngestRouteId::HeliusTransactionHydrated => prepare_helius(management, &environment, &composite, &transport, network, commitment),
crate::RawIngestRouteId::HttpBlockPolling => prepare_http_polling(&transport, network, commitment),
};
if let std::result::Result::Err(error) = prepared {
return route_start_error("Cannot reconstruct Start-time Transport resources", error);
}
ksp_logging_lib::debug!(
target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_ROUTE_START,
profile_id = request.profile_id.as_str(),
network,
route_id = request.route_id.as_str(),
inventory_generation = request.inventory_generation,
commitment = request.commitment.as_str(),
"revalidated Start request and reconstructed exact Transport-owned Worker source contract without runtime I/O"
);
return std::result::Result::Ok(crate::RawIngestRouteStartValidationDto {
commitment: request.commitment,
inventory_generation: request.inventory_generation,
network: network.to_owned(),
profile_id: request.profile_id.clone(),
route_id: request.route_id,
});
}
fn prepare_standard_logs(
transport: &ksp_config_lib::ResolvedTransportConfig,
network: &str,
commitment: ksp_onchain_transport_lib::SolanaCommitment,
) -> ksp_core_lib::Result<()> {
let endpoint = single_ws_endpoint(
transport,
network,
ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard,
ksp_onchain_transport_lib::WsSubscriptionKind::Logs,
);
let endpoint = match endpoint {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let role = single_http_role(transport.http_settings(), network, &["getTransaction"]);
let role = match role {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let pool = ksp_onchain_transport_lib::HttpTransportPool::new(transport.http_settings().clone());
let pool = match pool {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let source = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestStandardLogsSource::new(
endpoint,
ksp_onchain_transport_lib::SolanaLogsSubscribeFilter::AllWithVotes,
commitment,
pool,
role,
);
let source = match source {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
std::mem::drop(source);
return std::result::Result::Ok(());
}
fn prepare_standard_block(
transport: &ksp_config_lib::ResolvedTransportConfig,
network: &str,
commitment: ksp_onchain_transport_lib::SolanaCommitment,
) -> ksp_core_lib::Result<()> {
let endpoint = single_ws_endpoint(
transport,
network,
ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard,
ksp_onchain_transport_lib::WsSubscriptionKind::Block,
);
let endpoint = match endpoint {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let source = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestStandardBlockSource::new(
endpoint,
ksp_onchain_transport_lib::SolanaBlockSubscribeFilter::All,
commitment,
);
let source = match source {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
std::mem::drop(source);
return std::result::Result::Ok(());
}
fn prepare_helius(
management: &ksp_config_lib::ConfigManagement,
environment: &ksp_config_lib::ConfigEnvironment,
composite: &ksp_config_lib::ResolvedConfigComposite,
base_transport: &ksp_config_lib::ResolvedTransportConfig,
network: &str,
commitment: ksp_onchain_transport_lib::SolanaCommitment,
) -> ksp_core_lib::Result<()> {
let transport = resolve_optional_transport(management, environment, composite, crate::COMPOSITE_COMPONENT_ID_TRANSPORT_HELIUS, network);
let transport = match transport {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let endpoint = single_ws_endpoint(
&transport,
network,
ksp_onchain_transport_lib::WsProtocolKind::HeliusLaserStream,
ksp_onchain_transport_lib::WsSubscriptionKind::HeliusTransaction,
);
let endpoint = match endpoint {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let role = single_http_role(base_transport.http_settings(), network, &["getTransaction"]);
let role = match role {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let pool = ksp_onchain_transport_lib::HttpTransportPool::new(base_transport.http_settings().clone());
let pool = match pool {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let filter = ksp_onchain_transport_lib::HeliusTransactionSubscribeFilter::new(
std::option::Option::None,
std::option::Option::None,
std::option::Option::None,
std::option::Option::None,
std::option::Option::None,
std::option::Option::None,
std::option::Option::None,
);
let source = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHeliusTransactionSource::new(endpoint, filter, commitment, pool, role);
let source = match source {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
std::mem::drop(source);
return std::result::Result::Ok(());
}
fn prepare_yellowstone(
management: &ksp_config_lib::ConfigManagement,
environment: &ksp_config_lib::ConfigEnvironment,
composite: &ksp_config_lib::ResolvedConfigComposite,
base_transport: &ksp_config_lib::ResolvedTransportConfig,
network: &str,
commitment: ksp_onchain_transport_lib::SolanaCommitment,
) -> ksp_core_lib::Result<()> {
let transport = resolve_optional_transport(management, environment, composite, crate::COMPOSITE_COMPONENT_ID_TRANSPORT_YELLOWSTONE, network);
let transport = match transport {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let endpoint = single_grpc_endpoint(&transport, network);
let endpoint = match endpoint {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let channel = ksp_onchain_transport_lib::YellowstoneGrpcChannel::prepare(&endpoint);
let channel = match channel {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let filter_name = ksp_onchain_transport_lib::YellowstoneSubscribeFilterName::new("raw-ingest-all-transactions");
let filter_name = match filter_name {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut request = ksp_onchain_transport_lib::YellowstoneSubscribeRequest::new();
let inserted = request.insert_transaction_filter(filter_name, ksp_onchain_transport_lib::YellowstoneSubscribeTransactionFilter::new());
if let std::result::Result::Err(error) = inserted {
return std::result::Result::Err(error);
}
request.set_commitment(std::option::Option::Some(commitment));
let role = single_http_role(base_transport.http_settings(), network, &["getTransaction"]);
let role = match role {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let pool = ksp_onchain_transport_lib::HttpTransportPool::new(base_transport.http_settings().clone());
let pool = match pool {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let source = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestYellowstoneSource::new(channel, request, pool, role);
let source = match source {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
std::mem::drop(source);
return std::result::Result::Ok(());
}
fn prepare_http_polling(
transport: &ksp_config_lib::ResolvedTransportConfig,
network: &str,
commitment: ksp_onchain_transport_lib::SolanaCommitment,
) -> ksp_core_lib::Result<()> {
let role = single_http_role(transport.http_settings(), network, &["getSlot", "getBlocksWithLimit", "getBlock"]);
let role = match role {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let pool = ksp_onchain_transport_lib::HttpTransportPool::new(transport.http_settings().clone());
let pool = match pool {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let source = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHttpBlockPollingSource::new(pool, role, commitment);
let source = match source {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
std::mem::drop(source);
return std::result::Result::Ok(());
}
fn resolve_optional_transport(
management: &ksp_config_lib::ConfigManagement,
environment: &ksp_config_lib::ConfigEnvironment,
composite: &ksp_config_lib::ResolvedConfigComposite,
component_id: &str,
network: &str,
) -> ksp_core_lib::Result<ksp_config_lib::ResolvedTransportConfig> {
let component = composite.component(component_id);
let component = match component {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_ROUTE_START_PREPARATION_FAILED,
"Raw Transaction Ingest Desk selected route has no declared Transport source",
));
},
};
if component.resolved().file_id().as_str() != ksp_config_lib::FILE_ID_STD_TRANSPORT {
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_ROUTE_START_PREPARATION_FAILED,
"Raw Transaction Ingest Desk selected route references an unexpected Transport document",
));
}
let resolved = management.engine().resolve_transport_config_profile(component.resolved(), environment);
let resolved = match resolved {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if !transport_matches_network(&resolved, network) {
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_ROUTE_START_PREPARATION_FAILED,
"Raw Transaction Ingest Desk provider Transport network no longer matches the selected route",
));
}
return std::result::Result::Ok(resolved);
}
fn single_ws_endpoint(
transport: &ksp_config_lib::ResolvedTransportConfig,
network: &str,
protocol: ksp_onchain_transport_lib::WsProtocolKind,
capability: ksp_onchain_transport_lib::WsSubscriptionKind,
) -> ksp_core_lib::Result<ksp_onchain_transport_lib::WsEndpointSettings> {
let settings = match transport.ws_settings() {
std::option::Option::Some(value) => value,
std::option::Option::None => return ambiguous_resource_error("Start-time route requires one WebSocket endpoint but none is configured"),
};
let matching = settings
.endpoints()
.iter()
.filter(|endpoint| {
return endpoint.enabled()
&& endpoint.cluster().as_str() == network
&& endpoint.protocol() == protocol
&& endpoint.supports_subscription(capability);
})
.collect::<std::vec::Vec<_>>();
if matching.len() != 1 {
return ambiguous_resource_error("Start-time route requires exactly one matching WebSocket endpoint in its selected Transport profile");
}
return std::result::Result::Ok(matching[0].clone());
}
fn single_grpc_endpoint(
transport: &ksp_config_lib::ResolvedTransportConfig,
network: &str,
) -> ksp_core_lib::Result<ksp_onchain_transport_lib::YellowstoneGrpcEndpointSettings> {
let settings = match transport.grpc_settings() {
std::option::Option::Some(value) => value,
std::option::Option::None => return ambiguous_resource_error("Start-time route requires one Yellowstone gRPC endpoint but none is configured"),
};
let matching = settings
.endpoints()
.iter()
.filter(|endpoint| return endpoint.enabled() && endpoint.cluster().as_str() == network)
.collect::<std::vec::Vec<_>>();
if matching.len() != 1 {
return ambiguous_resource_error("Start-time route requires exactly one matching Yellowstone gRPC endpoint in its selected Transport profile");
}
return std::result::Result::Ok(matching[0].clone());
}
fn single_http_role(
settings: &ksp_onchain_transport_lib::HttpTransportSettings,
network: &str,
methods: &[&'static str],
) -> ksp_core_lib::Result<ksp_onchain_transport_lib::HttpRoleName> {
let mut request_kinds = std::vec::Vec::<&'static str>::with_capacity(methods.len());
for method_name in methods {
let descriptor = ksp_onchain_transport_lib::find_http_rpc_method(method_name);
let descriptor = match descriptor {
std::option::Option::Some(value) => value,
std::option::Option::None => return ambiguous_resource_error("Start-time route references an unknown HTTP method requirement"),
};
request_kinds.push(descriptor.request_kind());
}
let mut roles = std::collections::BTreeSet::<String>::new();
for endpoint in settings.endpoints() {
if !endpoint.enabled() || endpoint.cluster().as_str() != network {
continue;
}
for role in endpoint.roles() {
if role.enabled() {
roles.insert(role.role().as_str().to_owned());
}
}
}
let matching = roles
.into_iter()
.filter(|role_name| {
return request_kinds
.iter()
.all(|request_kind| return http_role_supports_request_kind(settings, network, role_name.as_str(), request_kind));
})
.collect::<std::vec::Vec<_>>();
if matching.len() != 1 {
return ambiguous_resource_error("Start-time route requires exactly one logical HTTP role satisfying its complete method set");
}
return std::result::Result::Ok(ksp_onchain_transport_lib::HttpRoleName::new(matching[0].clone()));
}
fn http_role_supports_request_kind(
settings: &ksp_onchain_transport_lib::HttpTransportSettings,
network: &str,
role_name: &str,
request_kind: &str,
) -> bool {
for endpoint in settings.endpoints() {
if !endpoint.enabled() || endpoint.cluster().as_str() != network {
continue;
}
for role in endpoint.roles() {
if !role.enabled() || role.role().as_str() != role_name {
continue;
}
if role.request_kinds().iter().any(|kind| return kind.is_wildcard() || kind.as_str() == request_kind) {
return true;
}
}
}
return false;
}
fn transport_matches_network(transport: &ksp_config_lib::ResolvedTransportConfig, network: &str) -> bool {
let mut networks = std::collections::BTreeSet::<String>::new();
for endpoint in transport.http_settings().endpoints() {
if endpoint.enabled() {
networks.insert(endpoint.cluster().as_str().to_owned());
}
}
if let std::option::Option::Some(settings) = transport.ws_settings() {
for endpoint in settings.endpoints() {
if endpoint.enabled() {
networks.insert(endpoint.cluster().as_str().to_owned());
}
}
}
if let std::option::Option::Some(settings) = transport.grpc_settings() {
for endpoint in settings.endpoints() {
if endpoint.enabled() {
networks.insert(endpoint.cluster().as_str().to_owned());
}
}
}
return networks.len() == 1 && networks.contains(network);
}
fn route_unavailable_error<T>() -> ksp_core_lib::Result<T> {
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_ROUTE_START_UNAVAILABLE,
"Raw Transaction Ingest Desk selected route is no longer Configured in the current Start-time inventory",
));
}
fn ambiguous_resource_error<T>(message: &'static str) -> ksp_core_lib::Result<T> {
return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_ROUTE_START_PREPARATION_FAILED, message));
}
fn route_start_error<T>(message: &'static str, source: ksp_core_lib::Error) -> ksp_core_lib::Result<T> {
return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_ROUTE_START_PREPARATION_FAILED, message).with_source(source));
}
#[cfg(test)]
#[path = "../unit_tests/route_start.rs"]
mod tests;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/tauri.rs
// version: 3
// version: 4
//! Tauri runtime assembly for the KSP Raw Transaction Ingest desktop application.
@@ -79,7 +79,8 @@ fn configure_commands(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tau
get_route_foundation,
get_route_inventory,
get_runtime_status,
splash_frontend_ready
splash_frontend_ready,
validate_route_start
]);
}
@@ -139,3 +140,17 @@ async fn splash_frontend_ready(
std::result::Result::Err(error) => std::result::Result::Err(project_command_error("splash_frontend_ready", crate::TRACING_DOMAIN_WINDOWS, &error)),
};
}
#[tauri::command]
async fn validate_route_start(
request: crate::RawIngestRouteStartRequestDto,
state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::RawIngestRouteStartValidationDto, crate::CommandErrorDto> {
let result = state.validate_route_start(&request);
return match result {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => {
std::result::Result::Err(project_command_error("validate_route_start", crate::TRACING_DOMAIN_ROUTE_START, &error))
},
};
}

View File

@@ -1,7 +1,7 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/tests/dependency_boundary.rs
// version: 2
// version: 3
//! Dependency-boundary canaries for Raw Transaction Ingest Desk Config-only route inventory.
//! Dependency-boundary canaries for Raw Transaction Ingest Desk Start-resource reconstruction.
#![forbid(unsafe_code)]
#![deny(unreachable_pub)]
@@ -21,51 +21,56 @@ fn read_text(path: &std::path::Path) -> String {
}
#[test]
fn pre_006_manifest_opens_only_transport_for_typed_config_composability() {
fn pre_007_manifest_adds_only_worker_vertical_to_existing_config_transport_boundary() {
let manifest = read_text(app_root().join("Cargo.toml").as_path());
for required in ["ksp-config-lib", "ksp-core-lib", "ksp-logging-lib", "ksp-onchain-transport-lib", "tauri-plugin-tracing"] {
assert!(manifest.contains(required), "missing pre.006 dependency {required}");
for required in [
"ksp-config-lib",
"ksp-core-lib",
"ksp-logging-lib",
"ksp-onchain-transport-lib",
"ksp-worker-raw-transaction-ingest-lib",
"tauri-plugin-tracing",
] {
assert!(manifest.contains(required), "missing pre.007 dependency {required}");
}
for forbidden in [
"ksp-store-lib",
"ksp-store-api",
"ksp-store-postgres-lib",
"ksp-worker-api",
"ksp-worker-raw-transaction-ingest-lib",
"ksp-job-backfill-lib",
"reqwest",
"tokio-postgres",
"tonic",
"yellowstone-grpc",
] {
assert!(!manifest.contains(forbidden), "pre.006 opens forbidden dependency {forbidden}");
assert!(!manifest.contains(forbidden), "pre.007 opens forbidden direct dependency {forbidden}");
}
}
#[test]
fn pre_006_production_sources_use_transport_settings_only_without_runtime_io_or_store_open() {
let root = app_root().join("src");
let entries = std::fs::read_dir(root.as_path());
assert!(entries.is_ok());
if let std::result::Result::Ok(entries) = entries {
for entry in entries.flatten() {
let path = entry.path();
if path.extension().and_then(std::ffi::OsStr::to_str) != std::option::Option::Some("rs") {
continue;
}
let source = read_text(path.as_path());
for forbidden in [
"HttpTransportPool",
"WsProtocolSession",
"YellowstoneGrpcChannel",
"Store::open",
"RawTransactionIngestWorker",
"RawTransactionIngestRuntimeResources",
"start_with_runtime_resources",
"request_stop",
] {
assert!(!source.contains(forbidden), "{} contains premature runtime marker {forbidden}", path.display());
}
}
fn pre_007_route_start_reconstructs_exact_worker_sources_without_network_store_or_worker_launch() {
let source = read_text(app_root().join("src/route_start.rs").as_path());
for required in [
"HttpTransportPool::new",
"YellowstoneGrpcChannel::prepare",
"RawTransactionIngestYellowstoneSource::new",
"RawTransactionIngestStandardLogsSource::new",
"RawTransactionIngestStandardBlockSource::new",
"RawTransactionIngestHeliusTransactionSource::new",
"RawTransactionIngestHttpBlockPollingSource::new",
] {
assert!(source.contains(required), "missing exact resource constructor {required}");
}
for forbidden in [
"YellowstoneGrpcChannel::connect",
"SolanaStandardWsSession::connect",
"HeliusWsSession::connect",
"Store::open",
"RawTransactionIngestWorker::",
"start_with_runtime_resources",
"request_stop",
] {
assert!(!source.contains(forbidden), "pre.007 performs premature runtime operation {forbidden}");
}
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/tests/desktop_contract.rs
// version: 3
// version: 4
//! Desktop scaffold contract canaries for Raw Transaction Ingest Desk.
@@ -169,3 +169,18 @@ fn pre_006_frontend_projects_profile_network_generation_and_safe_route_reasons()
assert!(!main.contains(forbidden), "frontend exposes forbidden physical marker: {forbidden}");
}
}
#[test]
fn pre_007_start_preflight_is_backend_owned_generation_bound_and_does_not_launch_worker() {
let dto = read_text(app_root().join("src/dto_route.rs").as_path());
for required in
["RawIngestCommitment", "RawIngestRouteStartRequestDto", "inventory_generation", "profile_id", "route_id", "RawIngestRouteStartValidationDto"]
{
assert!(dto.contains(required), "missing pre.007 Start DTO marker: {required}");
}
let tauri = read_text(app_root().join("src/tauri.rs").as_path());
assert!(tauri.contains("validate_route_start"));
for forbidden in ["start_worker", "stop_worker", "start_with_runtime_resources", "Store::open"] {
assert!(!tauri.contains(forbidden), "pre.007 Tauri surface contains premature runtime marker {forbidden}");
}
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/tests/desktop_security.rs
// version: 3
// version: 4
//! Desktop security canaries for the Raw Transaction Ingest Desk scaffold.
@@ -82,3 +82,31 @@ fn pre_006_route_inventory_dto_surface_excludes_endpoint_secret_and_physical_sto
assert!(!dto.contains(forbidden), "route DTO contains forbidden field marker {forbidden}");
}
}
#[test]
fn pre_007_start_request_and_acknowledgement_are_logical_only_and_resource_free() {
let dto = read_text(app_root().join("src/dto_route.rs").as_path());
for required in ["profile_id", "route_id", "inventory_generation", "commitment", "network"] {
assert!(dto.contains(required), "missing safe Start field marker {required}");
}
for forbidden in [
"endpoint_name",
"endpoint_url",
"api_key",
"connection_uri",
"source_key",
"secret_metadata",
"worker_handle",
"http_pool",
"ws_endpoint",
"yellowstone_channel",
] {
assert!(!dto.contains(forbidden), "Start DTO exposes forbidden physical marker {forbidden}");
}
let route_start = read_text(app_root().join("src/route_start.rs").as_path());
assert!(route_start.contains("ERROR_CODE_ROUTE_START_STALE_INVENTORY"));
assert!(route_start.contains("build_route_inventory_with_environment"));
assert!(route_start.contains("resolve_store_config_profile"));
assert!(!route_start.contains("with_context("endpoint"));
assert!(!route_start.contains("with_context("provider"));
}

View File

@@ -1,7 +1,7 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/tests/release_completeness.rs
// version: 3
// version: 4
//! Release-completeness canaries for Raw Transaction Ingest Desk Config-only route inventory.
//! Release-completeness canaries for Raw Transaction Ingest Desk Start-resource reconstruction.
#![forbid(unsafe_code)]
#![deny(unreachable_pub)]
@@ -21,7 +21,7 @@ fn read_text(path: &std::path::Path) -> String {
}
#[test]
fn pre_006_production_module_inventory_adds_only_route_inventory_to_the_scaffold() {
fn pre_007_production_module_inventory_adds_only_route_start_to_pre_006_surface() {
let lib = read_text(app_root().join("src/lib.rs").as_path());
let expected = [
"mod app_state;",
@@ -33,6 +33,7 @@ fn pre_006_production_module_inventory_adds_only_route_inventory_to_the_scaffold
"mod frontend_logging;",
"mod logging_runtime;",
"mod route_inventory;",
"mod route_start;",
"mod splash;",
"mod tauri;",
"mod tw_main;",
@@ -45,46 +46,44 @@ fn pre_006_production_module_inventory_adds_only_route_inventory_to_the_scaffold
}
#[test]
fn pre_006_public_surface_still_exposes_only_application_run_entry_point() {
fn pre_007_public_surface_still_exposes_only_application_run_entry_point() {
let lib = read_text(app_root().join("src/lib.rs").as_path());
let public_reexports = lib.lines().filter(|line| return line.starts_with("pub use ")).collect::<std::vec::Vec<_>>();
assert_eq!(public_reexports, vec!["pub use self::tauri::run;"]);
}
#[test]
fn pre_006_config_inventory_keeps_start_stop_and_runtime_resources_for_later_tranches() {
let app = read_text(app_root().join("src/app_state.rs").as_path());
let tauri = read_text(app_root().join("src/tauri.rs").as_path());
for forbidden in [
"RawTransactionIngestWorker",
"RawTransactionIngestRuntimeResources",
"request_stop",
"start_with_runtime_resources",
"Store::open",
"HttpTransportPool",
"WsProtocolSession",
"YellowstoneGrpcChannel",
fn pre_007_reconstructs_all_five_worker_source_contracts_without_advancing_store_or_worker_lifecycle() {
let route_start = read_text(app_root().join("src/route_start.rs").as_path());
for required in [
"RawTransactionIngestYellowstoneSource::new",
"RawTransactionIngestStandardLogsSource::new",
"RawTransactionIngestStandardBlockSource::new",
"RawTransactionIngestHeliusTransactionSource::new",
"RawTransactionIngestHttpBlockPollingSource::new",
"SolanaLogsSubscribeFilter::AllWithVotes",
"SolanaBlockSubscribeFilter::All",
"YellowstoneSubscribeTransactionFilter::new",
] {
assert!(!app.contains(forbidden));
assert!(!tauri.contains(forbidden));
assert!(route_start.contains(required), "missing pre.007 exact Worker resource marker {required}");
}
for forbidden in ["Store::open", "start_with_runtime_resources", "request_stop", "RawTransactionIngestRuntimeResources::new"] {
assert!(!route_start.contains(forbidden), "pre.007 advanced premature lifecycle marker {forbidden}");
}
}
#[test]
fn pre_006_inventory_source_is_config_only_and_contains_all_five_capability_requirements() {
let inventory = read_text(app_root().join("src/route_inventory.rs").as_path());
fn pre_007_start_revalidation_reuses_inventory_and_requires_profile_network_identity() {
let route_start = read_text(app_root().join("src/route_start.rs").as_path());
for required in [
"WsSubscriptionKind::Logs",
"WsSubscriptionKind::Block",
"WsSubscriptionKind::HeliusTransaction",
"getTransaction",
"getBlocksWithLimit",
"MissingYellowstoneGrpc",
"MissingRequiredSecret",
"current_generation == 0",
"request.inventory_generation != current_generation",
"build_route_inventory_with_environment",
"request.profile_id",
"resolve_store_config_profile",
"store.settings().network().as_str() != network",
"transport_matches_network",
] {
assert!(inventory.contains(required), "missing pre.006 inventory requirement {required}");
}
for forbidden in ["connect(", "Store::open", "start_with_runtime_resources", "request_stop"] {
assert!(!inventory.contains(forbidden), "pre.006 inventory performs premature runtime operation {forbidden}");
assert!(route_start.contains(required), "missing pre.007 revalidation marker {required}");
}
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/unit_tests/route_inventory.rs
// version: 4
// version: 5
#[test]
fn pre_006_route_requirement_projection_is_fail_closed_and_ordered() {
@@ -27,8 +27,12 @@ fn pre_006_route_requirement_projection_is_fail_closed_and_ordered() {
#[test]
fn pre_006_unavailable_profile_projects_all_five_routes_without_physical_material() {
let profile =
super::unavailable_profile("mainnet", false, std::option::Option::Some("mainnet"), crate::RawIngestRouteUnavailableReason::MissingRequiredSecret);
let profile = super::unavailable_profile(
"mainnet",
false,
std::option::Option::Some("mainnet"),
crate::RawIngestRouteUnavailableReason::MissingRequiredSecret,
);
assert_eq!(profile.routes.len(), 5);
assert_eq!(profile.network.as_deref(), std::option::Option::Some("mainnet"));
for route in profile.routes {
@@ -51,7 +55,7 @@ fn pre_006_fix_003_network_profiles_aggregate_same_network_capabilities_and_omit
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let inventory = super::build_route_inventory_with_environment(&management, &environment, 7);
let inventory = crate::build_route_inventory_with_environment(&management, &environment, 7);
assert!(inventory.is_ok());
let inventory = match inventory {
std::result::Result::Ok(value) => value,
@@ -59,10 +63,7 @@ fn pre_006_fix_003_network_profiles_aggregate_same_network_capabilities_and_omit
};
assert_eq!(inventory.generation, 7);
assert_eq!(inventory.default_profile, "devnet");
assert_eq!(
inventory.profiles.iter().map(|profile| return profile.profile_id.as_str()).collect::<std::vec::Vec<_>>(),
vec!["devnet", "mainnet", "testnet"]
);
assert_eq!(inventory.profiles.iter().map(|profile| return profile.profile_id.as_str()).collect::<std::vec::Vec<_>>(), vec!["devnet", "mainnet", "testnet"]);
let devnet = profile(&inventory, "devnet");
assert!(devnet.is_some());
if let std::option::Option::Some(devnet) = devnet {
@@ -130,7 +131,7 @@ fn pre_006_fix_003_base_network_routes_remain_configured_independently_of_option
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let inventory = super::build_route_inventory_with_environment(&management, &environment, 11);
let inventory = crate::build_route_inventory_with_environment(&management, &environment, 11);
assert!(inventory.is_ok());
let inventory = match inventory {
std::result::Result::Ok(value) => value,

View File

@@ -0,0 +1,65 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/unit_tests/route_start.rs
// version: 1
#[test]
fn pre_007_stale_generation_is_rejected_before_config_or_transport_reconstruction() {
let management = crate::config_management(&[]);
assert!(management.is_ok());
let management = match management {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let request = crate::RawIngestRouteStartRequestDto {
commitment: crate::RawIngestCommitment::Confirmed,
inventory_generation: 3,
profile_id: "devnet".to_owned(),
route_id: crate::RawIngestRouteId::StandardBlockDirect,
};
let result = crate::validate_route_start(&management, 2, &request);
assert!(result.is_err());
if let std::result::Result::Err(error) = result {
assert_eq!(error.code(), crate::ERROR_CODE_ROUTE_START_STALE_INVENTORY);
}
}
#[test]
fn pre_007_commitments_are_bounded_to_confirmed_and_finalized() {
assert_eq!(crate::RawIngestCommitment::Confirmed.into_transport(), ksp_onchain_transport_lib::SolanaCommitment::Confirmed);
assert_eq!(crate::RawIngestCommitment::Finalized.into_transport(), ksp_onchain_transport_lib::SolanaCommitment::Finalized);
assert_eq!(crate::RawIngestCommitment::Confirmed.as_str(), "confirmed");
assert_eq!(crate::RawIngestCommitment::Finalized.as_str(), "finalized");
}
#[test]
fn pre_007_public_standard_profiles_reconstruct_block_and_http_sources_without_network_io() {
let management = crate::config_management(&[]);
assert!(management.is_ok());
let management = match management {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let inventory = crate::build_route_inventory(&management, 1);
assert!(inventory.is_ok());
let inventory = match inventory {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
for profile in inventory.profiles {
for route_id in [crate::RawIngestRouteId::StandardLogsHydrated, crate::RawIngestRouteId::StandardBlockDirect, crate::RawIngestRouteId::HttpBlockPolling] {
let route = profile.routes.iter().find(|route| return route.route_id == route_id);
assert!(route.is_some(), "missing base route for {}", profile.profile_id);
if let std::option::Option::Some(route) = route {
assert_eq!(route.state, crate::RawIngestRouteState::Configured);
let request = crate::RawIngestRouteStartRequestDto {
commitment: crate::RawIngestCommitment::Confirmed,
inventory_generation: 1,
profile_id: profile.profile_id.clone(),
route_id,
};
let prepared = crate::validate_route_start(&management, 1, &request);
assert!(prepared.is_ok(), "failed Start-time reconstruction for {} / {}", profile.profile_id, route_id.as_str());
}
}
}
}

157
deltas/0.3.15/pre.007.md Normal file
View File

@@ -0,0 +1,157 @@
<!-- file: deltas/0.3.15/pre.007.md -->
<!-- version: 1 -->
# Delta `0.3.15-pre.007`
## Objet
Revalider au Start la sélection logique issue de l'inventaire `pre.006`, reconstruire les ressources Transport exactes attendues par les cinq familles Worker V1, puis les valider via les constructeurs publics du Worker sans ouvrir Store ni lancer de Worker.
## Requête Start sûre
Le préflight backend reçoit uniquement :
```text
profile_id
route_id
inventory_generation
commitment = confirmed | finalized
```
`profile_id` est ajouté au contrat candidat de `pre.001`. Depuis le passage réseau-centrique de `pre.006-fix.001`, un même `route_id` peut exister sur plusieurs réseaux ; le backend doit donc recevoir l'identité logique du profil réseau sélectionné pour reconstruire sans état frontend implicite.
Aucun endpoint, URL, provider choisi par le frontend, credential, token, metadata secrète, URI Store, source key ou handle runtime ne traverse IPC.
## Préflight `validate_route_start`
Le backend :
1. refuse une génération nulle ou différente de la génération courante avec `stale_inventory` ;
2. capture `ConfigEnvironment` ;
3. reconstruit l'inventaire avec le même snapshot d'environnement et la même génération ;
4. vérifie que le profil et la route existent encore et restent `Configured` ;
5. recharge le composite réseau explicite ;
6. résout Store et reproouve le réseau logique ;
7. résout le Transport de base et les sources provider-specific nécessaires ;
8. reconstruit la ressource exacte de la famille Worker ;
9. détruit immédiatement la ressource après validation ;
10. renvoie uniquement une projection sûre profil/réseau/route/génération/commitment.
Les erreurs lower-layer sont encapsulées derrière les codes/messages bornés du Desk ; les contextes physiques ne sont pas projetés au frontend.
## Reconstruction exacte des cinq routes
```text
yellowstone-hydrated
YellowstoneGrpcChannel::prepare
YellowstoneSubscribeRequest transaction-bearing
HttpTransportPool same-network / getTransaction
RawTransactionIngestYellowstoneSource::new
standard-logs-hydrated
endpoint SolanaStandard + capability Logs
SolanaLogsSubscribeFilter::AllWithVotes
HttpTransportPool same-network / getTransaction
RawTransactionIngestStandardLogsSource::new
standard-block-direct
endpoint SolanaStandard + capability Block
SolanaBlockSubscribeFilter::All
RawTransactionIngestStandardBlockSource::new
helius-transaction-hydrated
source transport.helius same-network
endpoint HeliusLaserStream + capability HeliusTransaction
filtre transaction général borné
HttpTransportPool de base same-network / getTransaction
RawTransactionIngestHeliusTransactionSource::new
http-block-polling
HttpTransportPool same-network
rôle unique composant getSlot + getBlocksWithLimit + getBlock
RawTransactionIngestHttpBlockPollingSource::new
```
HTTP conserve la sélection physique de ses endpoints dans `HttpTransportPool`. Pour WS et Yellowstone gRPC, `pre.007` exige exactement un endpoint activé matching dans le profil source résolu ; une ambiguïté est refusée au lieu d'être arbitrée silencieusement par le Desk.
## Frontières maintenues
Cette tranche n'introduit pas :
```text
Store::open
connexion WebSocket active
YellowstoneGrpcChannel::connect
RawTransactionIngestRuntimeResources
RawTransactionIngestWorker::start_with_runtime_resources
Worker handle
request_stop
état Running
multi-route runtime
```
Le Store lifecycle et le Start/Stop mono-route réel restent la responsabilité de `pre.008`.
## Dépendances
Le Desk ajoute uniquement la dépendance directe :
```text
ksp-worker-raw-transaction-ingest-lib
```
Aucune dépendance directe vers `ksp-store-lib`, `ksp-store-api`, un backend Store physique, `ksp-worker-api`, Job Backfill, `reqwest`, `tonic` ou Yellowstone provider crate n'est ajoutée.
## Tests et canaris
Ajouts/corrections :
- canari génération stale avant toute reconstruction ;
- commitments strictement bornés à `confirmed` / `finalized` ;
- reconstruction sans I/O des routes Standard Block et HTTP Polling sur les profils réseau committés ;
- inventaire de modules mis à jour avec `route_start` ;
- surface publique toujours limitée à `run` ;
- vérification statique des cinq constructeurs Worker exacts ;
- vérification de l'absence de Store/Worker launch ;
- sécurité DTO Start/ack sans matériel physique ;
- accès aux helpers crate-shared via `crate::` conformément aux règles Rust KSP.
## Version
```text
header racine : 605 -> 606
workspace : 0.3.15-pre.6.fix.3 -> 0.3.15-pre.7
```
## Validations disponibles 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 ...
clean
JSON/TOML parse
clean
```
La toolchain Cargo/rustfmt n'est pas disponible dans l'environnement d'assemblage ; aucune commande Cargo locale n'est donc déclarée PASS.
## Gate opérateur attendu
```bash
cargo fmt --all
cargo fmt --all -- --check
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.3.15
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-app-raw-transaction-ingest-desk --all-targets --all-features
cargo test --workspace --all-targets --all-features
(cd crates/ksp-app-raw-transaction-ingest-desk && cargo tauri dev)
```
Aucun `npm run build` direct n'est exécuté ; Tauri possède le cycle frontend.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/036-V0_3_15_RAW_TRANSACTION_INGEST_DESK_PLAN.md -->
<!-- version: 9 -->
<!-- version: 10 -->
# Plan v0.3.15 — Raw Transaction Ingest Desk
@@ -414,7 +414,7 @@ route_id -> Worker handle + snapshot source + terminal task ownership
Start d'une route :
```text
1. recevoir route_id + inventory_generation + commitment
1. recevoir profile_id + route_id + inventory_generation + commitment
2. re-résoudre la Config côté Rust
3. vérifier que la génération/route existe toujours
4. revalider réseau Transport/Store
@@ -523,7 +523,7 @@ Le texte détaillé des erreurs provider n'est jamais projeté tel quel.
|---------------------------------|---------------------|------------------------------------------------------------------------------|------------------------------------------|
| `RawIngestDeskOptionsDto` | backend -> frontend | profils logiques sûrs, réseaux, commitments supportés, génération inventaire | URL, secret, Store URI |
| `RawIngestRouteDto` | backend -> frontend | route_id, family, label, network, state, selectable, reason code | endpoint name/URL/token/source key |
| `RawIngestRouteStartRequestDto` | frontend -> backend | route_id, inventory_generation, commitment | endpoint/credential/client |
| `RawIngestRouteStartRequestDto` | frontend -> backend | profile_id, route_id, inventory_generation, commitment | endpoint/credential/client |
| `RawIngestRouteStopRequestDto` | frontend -> backend | route_id | Worker handle |
| `RawIngestRouteRuntimeDto` | backend -> frontend | route_id, lifecycle/health/activity, compteurs source-neutral | payload brut, erreur provider arbitraire |
| `CommandErrorDto` | backend -> frontend | code stable + contexte whiteliste | texte remote arbitraire |
@@ -746,9 +746,23 @@ Une route liée à un provider n'est projetée que si le composite réseau décl
### `pre.007` — reconstruction Start des ressources Transport
Revalider Config au Start et reconstruire les ressources Transport exactes nécessaires aux routes applicables du réseau sélectionné, dans le catalogue global de cinq IDs V1, sans lancer encore le Worker. Le choix des endpoints physiques reste Transport-owned.
**État : implémenté.** Le backend introduit un préflight `validate_route_start` qui reçoit une sélection logique sûre (`profile_id`, `route_id`, `inventory_generation`, `commitment`), refuse une génération stale, recharge le même snapshot d'environnement Config, reconstruit l'inventaire au même numéro puis revalide Store et Transport sur le réseau sélectionné.
Budget cible : une tranche intégration Transport/Desk.
Le `profile_id` est ajouté au DTO de Start par rapport au candidat de `pre.001` : après le passage réseau-centrique de `pre.006-fix.001`, un `route_id` existe sur plusieurs réseaux et ne peut donc pas identifier seul la composition à reconstruire. Ce champ est un identifiant logique sûr (`devnet`, `mainnet`, `testnet`) et ne révèle aucun provider ni endpoint.
La reconstruction exacte réutilise les contrats lower-layer existants :
```text
yellowstone-hydrated -> YellowstoneGrpcChannel::prepare + RawTransactionIngestYellowstoneSource::new
standard-logs-hydrated -> HttpTransportPool + RawTransactionIngestStandardLogsSource::new
standard-block-direct -> RawTransactionIngestStandardBlockSource::new
helius-transaction-hydrated -> HttpTransportPool + RawTransactionIngestHeliusTransactionSource::new
http-block-polling -> HttpTransportPool + RawTransactionIngestHttpBlockPollingSource::new
```
HTTP conserve son pool Transport-owned. Pour WS/gRPC, le profil source doit produire exactement un endpoint activé correspondant au protocole/capability/réseau attendu ; le Desk refuse l'ambiguïté au lieu d'inventer un choix physique. Les constructeurs Worker reçoivent des presets backend bornés (`AllWithVotes`, `All`, filtre transaction général, commitment confirmed/finalized).
Le préflight instancie puis détruit immédiatement le contrat source. Il ne fait aucun `connect`, aucun `Store::open`, aucun `start_with_runtime_resources`, ne conserve aucun handle et ne publie jamais `Running`. Le Start réel, l'ouverture Store et le lifecycle mono-route restent `pre.008`.
### `pre.008` — Store lifecycle et Start/Stop mono-route

View File

@@ -1,5 +1,5 @@
<!-- file: docs/validation/032-V0_3_15_RAW_TRANSACTION_INGEST_DESK.md -->
<!-- version: 10 -->
<!-- version: 11 -->
# Validation v0.3.15 — Raw Transaction Ingest Desk
@@ -315,7 +315,7 @@ frontend sans endpoints/secrets/handles/source keys/payloads bruts
|---------------------------------|---------------------|------------------------------------------------------------------------------|------------------------------------------|
| `RawIngestDeskOptionsDto` | backend -> frontend | profils logiques sûrs, réseaux, commitments supportés, génération inventaire | URL, secret, Store URI |
| `RawIngestRouteDto` | backend -> frontend | route_id, family, label, network, state, selectable, reason code | endpoint name/URL/token/source key |
| `RawIngestRouteStartRequestDto` | frontend -> backend | route_id, inventory_generation, commitment | endpoint/credential/client |
| `RawIngestRouteStartRequestDto` | frontend -> backend | profile_id, route_id, inventory_generation, commitment | endpoint/credential/client |
| `RawIngestRouteStopRequestDto` | frontend -> backend | route_id | Worker handle |
| `RawIngestRouteRuntimeDto` | backend -> frontend | route_id, lifecycle/health/activity, compteurs source-neutral | payload brut, erreur provider arbitraire |
| `CommandErrorDto` | backend -> frontend | code stable + contexte whiteliste | texte remote arbitraire |
@@ -384,7 +384,22 @@ Le catalogue global reste exactement cinq IDs V1, mais l'inventaire d'un profil
### `pre.007` — ressources Start
Reconstruction/revalidation Start des ressources Transport pour les routes applicables du réseau sélectionné, dans le catalogue global de cinq IDs V1, sans launch Worker.
**Statut : implémenté, gate opérateur à exécuter.** Le préflight backend `validate_route_start` est generation-bound et revalide la sélection contre un inventaire Config reconstruit avant toute ressource. La requête contient désormais `profile_id + route_id + inventory_generation + commitment` : `profile_id` est nécessaire depuis que les profils applicatifs sont réseau-centriques et qu'un même route ID existe sur plusieurs réseaux.
La preuve Start-time exige ensuite :
```text
Store Config résolu et réseau identique
Transport de base résolu et same-network
source provider-specific résolue seulement pour Helius/Yellowstone
HTTP role unique satisfaisant l'ensemble des méthodes requises
WS/gRPC endpoint unique correspondant au protocole/capability/réseau
constructeur public Worker de la famille exacte accepté
```
Les cinq constructeurs source Worker sont effectivement réutilisés. `HttpTransportPool::new` et `YellowstoneGrpcChannel::prepare` restent sans requête réseau ; la source reconstruite est détruite immédiatement après validation. Sont encore interdits dans cette tranche : `Store::open`, connexion WS/gRPC active, `RawTransactionIngestRuntimeResources`, `RawTransactionIngestWorker::start_with_runtime_resources`, handle Worker, Start/Stop réel et état `Running`.
Attendus de sécurité : la réponse de préflight ne contient que profil logique, réseau, route, génération et commitment ; les erreurs lower-layer sont remappées derrière des codes/messages app bornés. Aucun endpoint, URL, credential, metadata secrète, URI Store, source key ou payload RAW ne traverse IPC.
### `pre.008` — runtime mono-route