v0.3.15-pre.008

This commit is contained in:
2026-09-13 22:28:21 +02:00
parent 35bd1bb226
commit c71fb2d8fe
23 changed files with 1055 additions and 81 deletions

View File

@@ -1,5 +1,5 @@
# file: crates/ksp-app-raw-transaction-ingest-desk/Cargo.toml
# version: 3
# version: 4
[package]
name = "ksp-app-raw-transaction-ingest-desk"
@@ -28,6 +28,8 @@ 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-store-lib = { path = "../ksp-store-lib" }
ksp-worker-api = { path = "../ksp-worker-api" }
ksp-worker-raw-transaction-ingest-lib = { path = "../ksp-worker-raw-transaction-ingest-lib" }
serde = { workspace = true, features = ["derive"] }
tauri.workspace = true

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-app-raw-transaction-ingest-desk/README.md -->
<!-- version: 6 -->
<!-- version: 7 -->
# `ksp-app-raw-transaction-ingest-desk`
@@ -20,14 +20,16 @@ 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 reconstruction Start sans I/O
Desk -> catalogue logique, revalidation Start et ownership du lifecycle mono/multi-route
Store -> persistance via la façade KSP
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.
Au Start, le backend revalide une sélection logique par `profile_id + route_id + inventory_generation + commitment`. Il recharge Config, reproouve le réseau Store/Transport, reconstruit le pool HTTP ou l'endpoint WS/gRPC exact requis, ouvre le Store via `ksp-store-lib`, exige un health `Ready` puis lance le Worker exact via `RawTransactionIngestWorker::start_with_runtime_resources`. Les ressources physiques et handles restent exclusivement côté Rust.
Le runtime est actuellement mono-route : un seul Start peut posséder le slot applicatif. Le handle Worker est installé avant l'accusé Start. Stop demande un arrêt coopératif, attend le terminal Worker, puis le monitor backend reprend l'unique `Arc<Store>` et exécute `Store::close()` avant de libérer le slot. Le partage d'un Store entre plusieurs Workers reste une responsabilité de la tranche multi-route suivante.
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.
@@ -46,7 +48,7 @@ http-block-polling
Les profils standard publics committés déclarent `Block` en plus de `Logs`; `standard-block-direct` est donc composable depuis Config sur Devnet, Mainnet et Testnet. Cette déclaration ne transforme pas `blockSubscribe` en méthode stable et ne remplace pas la revalidation runtime.
Une route `Configured` est uniquement **composable depuis Config**. La disponibilité réelle des connexions, du Store et du Worker est revalidée lors du Start avant qu'une route puisse devenir `Running`.
Une route `Configured` est uniquement **composable depuis Config**. La disponibilité réelle du Store et du Worker est revalidée lors du Start avant publication de l'accusé runtime. Les états spontanés après Start ne sont pas encore streamés vers le frontend ; l'observabilité continue est ajoutée par la tranche monitoring dédiée.
## Ports de développement

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-app-raw-transaction-ingest-desk/USAGE.md -->
<!-- version: 5 -->
<!-- version: 6 -->
# Utilisation de `ksp-app-raw-transaction-ingest-desk`
@@ -29,13 +29,21 @@ raison sûre lorsque la route est indisponible
génération d'inventaire
```
`Configured` signifie uniquement que toutes les requirements Config nécessaires à la route sont cohérentes. Les profils `solana-public` standard committés déclarent explicitement les capabilities `Logs` et `Block`; `standard-block-direct` est donc Configured sur les trois réseaux logiques tant que ce socle Config reste valide. Aucun probe réseau, aucune ouverture de Store et aucun Worker ne sont déclenchés par l'inventaire.
`Configured` signifie uniquement que toutes les requirements Config nécessaires à la route sont cohérentes. Les profils `solana-public` standard committés déclarent explicitement les capabilities `Logs` et `Block`; `standard-block-direct` est donc Configured sur les trois réseaux logiques tant que ce socle Config reste valide. L'inventaire seul ne fait aucun probe réseau et n'ouvre aucun Store ni Worker.
Un refresh reconstruit l'inventaire depuis Config et publie une nouvelle génération. La sélection visuelle d'un réseau ne choisit aucune URL, provider ou ressource physique et ne démarre aucune acquisition. Une capability optionnelle dont le secret n'est pas résolu rend uniquement la route qui en dépend indisponible ; les routes satisfaites par le Transport de base du même réseau restent composables.
Une route provider-specific dont aucune source nest déclarée pour le réseau nest pas une erreur de configuration : elle est hors inventaire pour ce réseau. Par exemple, Helius Transaction est projetée sur Devnet/Mainnet lorsque `transport.helius` existe, mais pas sur Testnet lorsquaucune source Helius Testnet nest déclarée.
Les URLs, tokens, metadata secrètes, handles Transport/Store/Worker, URI Store, source keys et payloads RAW restent backend-only.
### Start/Stop mono-route
Pour une route `Configured`, choisir `confirmed` ou `finalized` puis utiliser `Start`. Le backend revalide la génération d'inventaire et la composition Config, ouvre le Store correspondant, exige son état `Ready`, construit le Worker exact et installe son handle avant de renvoyer l'accusé runtime.
Un seul Worker peut être actif ou en démarrage dans cette tranche. Un second Start est refusé tant que le Worker précédent n'est pas terminal et que son Store n'a pas été explicitement fermé. `Stop` est coopératif : la commande attend le terminal Worker et la fermeture Store avant de rendre le slot à nouveau disponible.
Le frontend n'effectue aucun polling runtime continu dans cette tranche. Une terminaison spontanée est nettoyée côté backend ; sa projection temps réel vers l'UI appartient au monitoring ultérieur.
## 3. Raisons d'indisponibilité
Les reasons exposées sont des codes applicatifs bornés, par exemple :

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-app-raw-transaction-ingest-desk/frontend/main.html -->
<!-- version: 4 -->
<!-- version: 5 -->
<!DOCTYPE html>
<html lang="fr">
@@ -51,18 +51,25 @@
<div class="d-flex align-items-start justify-content-between gap-3 flex-wrap mb-4">
<div>
<h1 class="h3 mb-1">Routes live</h1>
<p class="text-body-secondary mb-0">Inventaire déterministe depuis Config, sans probe réseau ni démarrage de Worker.</p>
<p class="text-body-secondary mb-0">Inventaire Config et runtime mono-route Store + Worker avec Start/Stop backend-owned.</p>
</div>
<button id="refreshRouteInventory" class="btn btn-outline-primary btn-sm" type="button">
<i class="fa-solid fa-rotate me-2" aria-hidden="true"></i>Refresh
</button>
</div>
<div class="row g-3 align-items-end mb-4">
<div class="col-12 col-md-6">
<div class="col-12 col-md-4">
<label class="form-label" for="routeProfile">Réseau logique</label>
<select id="routeProfile" class="form-select" aria-label="Réseau Raw Transaction Ingest Desk"></select>
</div>
<div class="col-6 col-md-3">
<div class="col-12 col-md-3">
<label class="form-label" for="routeCommitment">Commitment</label>
<select id="routeCommitment" class="form-select" aria-label="Commitment Raw Transaction Ingest Desk">
<option value="confirmed" selected>confirmed</option>
<option value="finalized">finalized</option>
</select>
</div>
<div class="col-6 col-md-2">
<div class="small text-body-secondary">Réseau</div>
<div id="routeNetwork" class="fw-semibold app-route-code"></div>
</div>
@@ -72,7 +79,21 @@
</div>
</div>
<div class="alert alert-info" role="status">
<strong>Configured</strong> signifie uniquement que Config prouve la composabilité. Les connexions, le Store et le Worker seront revalidés au Start dans les tranches suivantes.
<strong>Configured</strong> signifie que Config prouve la composabilité. Start revalide Config, ouvre le Store puis lance exactement un Worker. pre.008 reste volontairement mono-route.
</div>
<div class="card border-primary-subtle mb-4">
<div class="card-body d-flex align-items-center justify-content-between gap-3 flex-wrap">
<div>
<div class="small text-body-secondary">Runtime mono-route</div>
<div class="d-flex flex-wrap gap-2 mt-1">
<span id="routeRuntimeState" class="badge text-bg-secondary">idle</span>
<span id="routeRuntimeIdentity" class="app-route-code small text-body-secondary">aucun Worker actif</span>
</div>
</div>
<button id="stopRoute" class="btn btn-outline-danger btn-sm" type="button" disabled>
<i class="fa-solid fa-stop me-2" aria-hidden="true"></i>Stop
</button>
</div>
</div>
<div class="small text-body-secondary mb-3">Réseau affiché : <span id="routeSelectedProfile" class="app-route-code"></span></div>
<div id="routeFoundation" class="row g-3 app-route-grid" aria-live="polite"></div>

View File

@@ -1,11 +1,15 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/frontend/ts/main.ts
// version: 4
// version: 5
import "bootstrap";
import ResizeObserver from "resize-observer-polyfill";
import "simplebar";
import type { ShellStatusDto } from "./bindings/ksp_app_raw_transaction_ingest_desk/dto_common/ShellStatusDto.ts";
import type { RawIngestCommitment } from "./bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestCommitment.ts";
import type { RawIngestRouteId } from "./bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestRouteId.ts";
import type { RawIngestRouteInventoryDto } from "./bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestRouteInventoryDto.ts";
import type { RawIngestRouteRuntimeDto } from "./bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestRouteRuntimeDto.ts";
import type { RawIngestRouteStartRequestDto } from "./bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestRouteStartRequestDto.ts";
import { frontendDebug, frontendInfo, frontendTrace, frontendWarn, installFrontendConsoleBridge } from "./frontend_log";
import { invokeKsp } from "./invoke";
@@ -19,6 +23,8 @@ const viewTitles: Record<ViewId, string> = {
diagnostics: "Diagnostics",
};
let activeRuntime: RawIngestRouteRuntimeDto | null = null;
let lastRuntime: RawIngestRouteRuntimeDto | null = null;
let routeInventory: RawIngestRouteInventoryDto | null = null;
let selectedProfileId: string | null = null;
@@ -137,6 +143,21 @@ function renderSelectedProfile(): void {
reason.textContent = reasonLabel(route.reason);
meta.append(state, network, reason);
body.append(title, code, meta);
if (route.selectable) {
const actions = document.createElement("div");
actions.className = "mt-3";
const start = document.createElement("button");
start.className = "btn btn-primary btn-sm";
start.type = "button";
start.textContent = "Start";
start.disabled = activeRuntime !== null;
start.dataset.routeId = route.routeId;
start.addEventListener("click", () => {
void startRoute(route.routeId).catch(() => frontendWarn("main", "Raw Transaction Ingest Desk route Start failed"));
});
actions.append(start);
body.append(actions);
}
card.append(body);
column.append(card);
routeRoot.append(column);
@@ -149,6 +170,87 @@ function renderSelectedProfile(): void {
});
}
function selectedCommitment(): RawIngestCommitment {
const selector = document.querySelector<HTMLSelectElement>("#routeCommitment");
return selector?.value === "finalized" ? "finalized" : "confirmed";
}
function runtimeIsActive(runtime: RawIngestRouteRuntimeDto): boolean {
return runtime.state === "starting" || runtime.state === "running" || runtime.state === "stopping";
}
function renderMonoRouteRuntime(): void {
const projected = activeRuntime ?? lastRuntime;
const state = document.querySelector<HTMLElement>("#routeRuntimeState");
if (state) {
state.textContent = projected?.state ?? "idle";
state.className = projected?.state === "faulted" ? "badge text-bg-danger" : activeRuntime ? "badge text-bg-primary" : "badge text-bg-secondary";
}
text(
"#routeRuntimeIdentity",
projected ? `${projected.profileId} / ${projected.routeId} / ${projected.commitment}` : "aucun Worker actif",
);
const stop = document.querySelector<HTMLButtonElement>("#stopRoute");
if (stop) {
stop.disabled = activeRuntime === null || activeRuntime.state === "stopping";
}
const profile = document.querySelector<HTMLSelectElement>("#routeProfile");
if (profile) {
profile.disabled = activeRuntime !== null;
}
const commitment = document.querySelector<HTMLSelectElement>("#routeCommitment");
if (commitment) {
commitment.disabled = activeRuntime !== null;
}
}
async function startRoute(routeId: RawIngestRouteId): Promise<void> {
if (routeInventory === null || selectedProfileId === null || activeRuntime !== null) {
return;
}
const request: RawIngestRouteStartRequestDto = {
commitment: selectedCommitment(),
inventoryGeneration: routeInventory.generation,
profileId: selectedProfileId,
routeId,
};
frontendDebug("main", "Raw Transaction Ingest Desk mono-route Start requested", {
commitment: request.commitment,
profileId: request.profileId,
routeId: request.routeId,
});
const response = await invokeKsp<RawIngestRouteRuntimeDto>("main", "start_route", { request });
lastRuntime = response;
activeRuntime = runtimeIsActive(response) ? response : null;
renderMonoRouteRuntime();
renderSelectedProfile();
frontendInfo("main", "Raw Transaction Ingest Desk mono-route Start acknowledged", {
profileId: response.profileId,
routeId: response.routeId,
state: response.state,
});
}
async function stopRoute(): Promise<void> {
if (activeRuntime === null) {
return;
}
frontendDebug("main", "Raw Transaction Ingest Desk mono-route Stop requested", {
profileId: activeRuntime.profileId,
routeId: activeRuntime.routeId,
});
const response = await invokeKsp<RawIngestRouteRuntimeDto>("main", "stop_route");
lastRuntime = response;
activeRuntime = runtimeIsActive(response) ? response : null;
renderMonoRouteRuntime();
renderSelectedProfile();
frontendInfo("main", "Raw Transaction Ingest Desk mono-route Stop completed", {
profileId: response.profileId,
routeId: response.routeId,
state: response.state,
});
}
function renderRouteInventory(inventory: RawIngestRouteInventoryDto): void {
routeInventory = inventory;
const requestedProfile = selectedProfileId;
@@ -169,6 +271,7 @@ function renderRouteInventory(inventory: RawIngestRouteInventoryDto): void {
text("#runtimeInventoryGeneration", inventory.generation.toString());
text("#runtimeInventoryDefaultProfile", inventory.defaultProfile);
renderSelectedProfile();
renderMonoRouteRuntime();
frontendTrace("main", "Raw Transaction Ingest Desk Config route inventory rendered", {
generation: inventory.generation,
profileCount: inventory.profiles.length,
@@ -220,6 +323,9 @@ function bindRefreshControls(): void {
document.querySelector<HTMLButtonElement>("#refreshRuntimeStatus")?.addEventListener("click", () => {
void refreshRuntimeStatus().catch(() => frontendWarn("main", "Raw Transaction Ingest Desk runtime status refresh failed"));
});
document.querySelector<HTMLButtonElement>("#stopRoute")?.addEventListener("click", () => {
void stopRoute().catch(() => frontendWarn("main", "Raw Transaction Ingest Desk route Stop failed"));
});
document.querySelector<HTMLSelectElement>("#routeProfile")?.addEventListener("change", event => {
const selector = event.currentTarget;
if (!(selector instanceof HTMLSelectElement)) {
@@ -239,7 +345,7 @@ async function initializeMain(): Promise<void> {
activateView("routes", "startup");
await refreshRouteInventory();
await refreshRuntimeStatus();
frontendInfo("main", "Raw Transaction Ingest Desk Config inventory frontend ready");
frontendInfo("main", "Raw Transaction Ingest Desk mono-route runtime frontend ready");
}
void initializeMain().catch(() => frontendWarn("main", "Raw Transaction Ingest Desk frontend initialization failed"));

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/app_state.rs
// version: 3
// version: 4
//! Shared backend state owned by the Raw Transaction Ingest Desk Tauri application.
@@ -9,6 +9,7 @@ pub(crate) struct AppState {
config_management: ksp_config_lib::ConfigManagement,
inventory_generation: std::sync::Mutex<u32>,
logging_runtime: std::sync::Mutex<LoggingRuntimeState>,
route_runtime: std::sync::Arc<crate::RouteRuntimeState>,
splash_settings: crate::SplashSettings,
splash_sequence_started: std::sync::atomic::AtomicBool,
}
@@ -55,6 +56,7 @@ impl crate::AppState {
fallback_active: logging_startup.fallback_active,
startup_diagnostic: logging_startup.startup_diagnostic,
}),
route_runtime: std::sync::Arc::new(crate::RouteRuntimeState::new()),
splash_settings,
splash_sequence_started: std::sync::atomic::AtomicBool::new(false),
});
@@ -113,6 +115,31 @@ impl crate::AppState {
return crate::validate_route_start(&self.config_management, generation, request);
}
/// Starts one real mono-route Store + Worker runtime after repeating the complete Start-time revalidation.
pub(crate) async fn start_route(&self, request: &crate::RawIngestRouteStartRequestDto) -> ksp_core_lib::Result<crate::RouteRuntimeLaunch> {
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",
));
},
};
let prepared = crate::prepare_route_start(&self.config_management, generation, request);
let prepared = match prepared {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return crate::start_route_runtime(std::sync::Arc::clone(&self.route_runtime), prepared).await;
}
/// Requests cooperative Stop and waits until the Worker is terminal and Store cleanup has released the mono-route slot.
pub(crate) async fn stop_route(&self) -> ksp_core_lib::Result<crate::RawIngestRouteRuntimeDto> {
return self.route_runtime.stop_and_wait().await;
}
/// 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();
@@ -146,7 +173,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.007-start-resource-revalidation".to_owned(),
shell_phase: "pre.008-mono-route-runtime".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: 4
// version: 5
//! 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 by mono-route Store and Worker lifecycle operations.
pub(crate) const TRACING_DOMAIN_ROUTE_RUNTIME: &str = "raw_transaction_ingest.route_runtime";
/// 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.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/dto_route.rs
// version: 4
// version: 5
//! Safe route-identity, Config-composability and lifecycle DTOs for Raw Transaction Ingest Desk.
@@ -129,6 +129,25 @@ pub(crate) struct RawIngestRouteStartValidationDto {
pub(crate) route_id: crate::RawIngestRouteId,
}
/// Safe mono-route runtime acknowledgement containing only logical identity and lifecycle state.
#[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/RawIngestRouteRuntimeDto.ts")]
pub(crate) struct RawIngestRouteRuntimeDto {
/// Confirmed/finalized commitment bound to the active Worker.
pub(crate) commitment: crate::RawIngestCommitment,
/// Inventory generation revalidated before Store opening.
pub(crate) inventory_generation: u32,
/// Logical network bound identically to Store and Worker.
pub(crate) network: String,
/// Safe logical network-profile identifier.
pub(crate) profile_id: String,
/// Exact logical route owned by the mono-route Worker.
pub(crate) route_id: crate::RawIngestRouteId,
/// Safe application-owned projection of current Worker lifecycle.
pub(crate) state: crate::RawIngestRouteState,
}
/// Stable application-owned route families used only for safe presentation and orchestration.
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, TS)]
#[serde(rename_all = "snake_case")]

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/errors.rs
// version: 3
// version: 4
//! Stable Raw Transaction Ingest Desk application error codes.
@@ -26,6 +26,23 @@ pub(crate) const ERROR_CODE_ROUTE_INVENTORY_INVALID: ksp_core_lib::ErrorCode =
/// 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");
/// A mono-route Worker runtime already owns the application slot.
pub(crate) const ERROR_CODE_ROUTE_RUNTIME_ACTIVE: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "route_runtime_active");
/// No mono-route Worker runtime is currently active.
pub(crate) const ERROR_CODE_ROUTE_RUNTIME_NOT_ACTIVE: ksp_core_lib::ErrorCode =
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "route_runtime_not_active");
/// Store or Worker startup failed after Start-time route revalidation.
pub(crate) const ERROR_CODE_ROUTE_RUNTIME_START_FAILED: ksp_core_lib::ErrorCode =
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "route_runtime_start_failed");
/// The opened Store did not prove Ready before Worker launch.
pub(crate) const ERROR_CODE_ROUTE_RUNTIME_STORE_NOT_READY: ksp_core_lib::ErrorCode =
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "route_runtime_store_not_ready");
/// The Store could not be reclaimed or explicitly closed after Worker termination.
pub(crate) const ERROR_CODE_ROUTE_RUNTIME_STORE_SHUTDOWN_FAILED: ksp_core_lib::ErrorCode =
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "route_runtime_store_shutdown_failed");
/// The mono-route runtime reservation/handle state changed unexpectedly.
pub(crate) const ERROR_CODE_ROUTE_RUNTIME_STATE_INVALID: ksp_core_lib::ErrorCode =
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "route_runtime_state_invalid");
/// 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");

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/lib.rs
// version: 4
// version: 5
//! 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_runtime;
mod route_start;
mod splash;
mod tauri;
@@ -57,6 +58,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 by mono-route Store and Worker lifecycle operations.
pub(crate) use self::constants::TRACING_DOMAIN_ROUTE_RUNTIME;
/// 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.
@@ -89,6 +92,8 @@ 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 mono-route runtime acknowledgement containing only logical identity and lifecycle.
pub(crate) use self::dto_route::RawIngestRouteRuntimeDto;
/// 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.
@@ -111,6 +116,18 @@ 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;
/// A mono-route Worker runtime already owns the application slot.
pub(crate) use self::errors::ERROR_CODE_ROUTE_RUNTIME_ACTIVE;
/// No mono-route Worker runtime is currently active.
pub(crate) use self::errors::ERROR_CODE_ROUTE_RUNTIME_NOT_ACTIVE;
/// Store or Worker startup failed after Start-time route revalidation.
pub(crate) use self::errors::ERROR_CODE_ROUTE_RUNTIME_START_FAILED;
/// The mono-route runtime reservation/handle state changed unexpectedly.
pub(crate) use self::errors::ERROR_CODE_ROUTE_RUNTIME_STATE_INVALID;
/// The opened Store did not prove Ready before Worker launch.
pub(crate) use self::errors::ERROR_CODE_ROUTE_RUNTIME_STORE_NOT_READY;
/// The Store could not be reclaimed or explicitly closed after Worker termination.
pub(crate) use self::errors::ERROR_CODE_ROUTE_RUNTIME_STORE_SHUTDOWN_FAILED;
/// 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.
@@ -137,6 +154,16 @@ pub(crate) use self::logging_runtime::launch_identity;
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;
/// Launch ownership for one mono-route Worker and its caller-owned Store.
pub(crate) use self::route_runtime::RouteRuntimeLaunch;
/// Shared mono-route runtime state admitting at most one starting or active Worker.
pub(crate) use self::route_runtime::RouteRuntimeState;
/// Opens Store and starts one exact route Worker after Start-time revalidation.
pub(crate) use self::route_runtime::start_route_runtime;
/// Fully revalidated Start preparation retaining Store settings and exact Worker resources.
pub(crate) use self::route_start::PreparedRouteStart;
/// Revalidates one Start request and retains its backend-only Store/Worker resources.
pub(crate) use self::route_start::prepare_route_start;
/// 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.

View File

@@ -0,0 +1,396 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/route_runtime.rs
// version: 1
//! Mono-route Store and Worker lifecycle owned by Raw Transaction Ingest Desk.
const ROUTE_STOP_CLEANUP_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(2);
const STORE_RECLAIM_POLL_INTERVAL: std::time::Duration = std::time::Duration::from_millis(5);
const STORE_RECLAIM_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(1);
/// Shared mono-route runtime state. `pre.008` admits at most one active or starting Worker.
pub(crate) struct RouteRuntimeState {
inner: std::sync::Mutex<RouteRuntimeInner>,
}
impl crate::RouteRuntimeState {
/// Creates an idle mono-route runtime state.
#[must_use]
pub(crate) fn new() -> Self {
return Self { inner: std::sync::Mutex::new(RouteRuntimeInner { next_sequence: 0, slot: RouteRuntimeSlot::Idle }) };
}
fn reserve(&self, prepared: &crate::PreparedRouteStart) -> ksp_core_lib::Result<RouteRuntimeReservation> {
let inner = self.inner.lock();
let mut inner = match inner {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(runtime_lock_error()),
};
if !matches!(inner.slot, RouteRuntimeSlot::Idle) {
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_ROUTE_RUNTIME_ACTIVE,
"Raw Transaction Ingest Desk already owns one mono-route Worker runtime",
));
}
let sequence = inner.next_sequence.checked_add(1);
let sequence = match sequence {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_ROUTE_RUNTIME_START_FAILED,
"Raw Transaction Ingest Desk Worker sequence is exhausted",
));
},
};
let worker_id = ksp_worker_api::WorkerId::new(format!("raw-ingest-desk-{sequence}"));
let worker_id = match worker_id {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(
ksp_core_lib::Error::new(crate::ERROR_CODE_ROUTE_RUNTIME_START_FAILED, "Cannot create Raw Transaction Ingest Desk Worker identity")
.with_source(error),
);
},
};
let identity = RouteRuntimeIdentity {
commitment: prepared.commitment,
inventory_generation: prepared.inventory_generation,
network: prepared.network.clone(),
profile_id: prepared.profile_id.clone(),
route_id: prepared.route_id,
};
inner.next_sequence = sequence;
inner.slot = RouteRuntimeSlot::Starting { token: sequence };
return std::result::Result::Ok(RouteRuntimeReservation { identity, token: sequence, worker_id });
}
fn activate(
&self,
reservation: &RouteRuntimeReservation,
handle: ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHandle,
) -> ksp_core_lib::Result<crate::RawIngestRouteRuntimeDto> {
let inner = self.inner.lock();
let mut inner = match inner {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(runtime_lock_error()),
};
match &inner.slot {
RouteRuntimeSlot::Starting { token } if *token == reservation.token => {},
RouteRuntimeSlot::Idle | RouteRuntimeSlot::Starting { .. } | RouteRuntimeSlot::Active { .. } => {
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_ROUTE_RUNTIME_STATE_INVALID,
"Raw Transaction Ingest Desk mono-route runtime reservation changed before Worker activation",
));
},
}
let state = project_worker_state(handle.snapshot_source().current().worker_snapshot().state());
inner.slot = RouteRuntimeSlot::Active { handle, identity: reservation.identity.clone(), token: reservation.token };
return std::result::Result::Ok(reservation.identity.dto(state));
}
fn rollback(&self, token: u64) {
let inner = self.inner.lock();
let mut inner = match inner {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
if matches!(&inner.slot, RouteRuntimeSlot::Starting { token: current, .. } if *current == token) {
inner.slot = RouteRuntimeSlot::Idle;
}
}
fn finish(&self, token: u64) {
let inner = self.inner.lock();
let mut inner = match inner {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
if matches!(&inner.slot, RouteRuntimeSlot::Active { token: current, .. } if *current == token) {
inner.slot = RouteRuntimeSlot::Idle;
}
}
/// Requests cooperative Stop, waits for terminal Worker state and returns after the terminal monitor has released the mono-route slot.
pub(crate) async fn stop_and_wait(&self) -> ksp_core_lib::Result<crate::RawIngestRouteRuntimeDto> {
let active = {
let inner = self.inner.lock();
let inner = match inner {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(runtime_lock_error()),
};
match &inner.slot {
RouteRuntimeSlot::Active { handle, identity, .. } => std::result::Result::Ok((handle.clone(), identity.clone())),
RouteRuntimeSlot::Idle | RouteRuntimeSlot::Starting { .. } => std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_ROUTE_RUNTIME_NOT_ACTIVE,
"Raw Transaction Ingest Desk has no active mono-route Worker to stop",
)),
}
};
let (handle, identity) = match active {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let _first_stop_request = handle.request_stop();
let terminal = handle.wait_terminal().await;
let terminal = match terminal {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(
ksp_core_lib::Error::new(crate::ERROR_CODE_ROUTE_RUNTIME_STATE_INVALID, "Cannot observe terminal Raw Transaction Ingest Desk Worker state")
.with_source(error),
);
},
};
let started = std::time::Instant::now();
loop {
if self.is_idle()? {
return std::result::Result::Ok(identity.dto(project_worker_state(terminal)));
}
if started.elapsed() >= ROUTE_STOP_CLEANUP_TIMEOUT {
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_ROUTE_RUNTIME_STORE_SHUTDOWN_FAILED,
"Raw Transaction Ingest Desk terminal cleanup did not release the mono-route slot",
));
}
tokio::time::sleep(STORE_RECLAIM_POLL_INTERVAL).await;
}
}
fn is_idle(&self) -> ksp_core_lib::Result<bool> {
let inner = self.inner.lock();
let inner = match inner {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(runtime_lock_error()),
};
return std::result::Result::Ok(matches!(inner.slot, RouteRuntimeSlot::Idle));
}
}
/// Launch ownership returned only after Store readiness, Worker creation and handle installation have succeeded.
pub(crate) struct RouteRuntimeLaunch {
acknowledgement: crate::RawIngestRouteRuntimeDto,
handle: ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHandle,
runtime_state: std::sync::Arc<crate::RouteRuntimeState>,
store: std::sync::Arc<ksp_store_lib::Store>,
token: u64,
}
impl crate::RouteRuntimeLaunch {
/// Returns the safe Start acknowledgement captured after the Worker handle was installed.
#[must_use]
pub(crate) fn acknowledgement(&self) -> crate::RawIngestRouteRuntimeDto {
return self.acknowledgement.clone();
}
/// Waits for terminal Worker state, explicitly closes Store after the Worker releases its `Arc`, then frees the mono-route slot.
pub(crate) async fn monitor(self) {
let terminal = self.handle.wait_terminal().await;
match terminal {
std::result::Result::Ok(state) => {
ksp_logging_lib::info!(
target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_ROUTE_RUNTIME,
worker_state = state.code(),
"Raw Transaction Ingest Desk mono-route Worker reached terminal state"
);
},
std::result::Result::Err(error) => {
ksp_logging_lib::warn!(
target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_ROUTE_RUNTIME,
error_domain = error.code().domain(),
error_code = error.code().code(),
"Raw Transaction Ingest Desk terminal Worker wait failed"
);
},
}
let close = close_store_arc(self.store).await;
if let std::result::Result::Err(error) = close {
ksp_logging_lib::warn!(
target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_ROUTE_RUNTIME,
error_domain = error.code().domain(),
error_code = error.code().code(),
"Raw Transaction Ingest Desk Store shutdown failed after terminal Worker"
);
}
self.runtime_state.finish(self.token);
}
}
/// Opens one Store, starts one exact Worker route and atomically installs its handle in the mono-route application slot.
pub(crate) async fn start_route_runtime(
runtime_state: std::sync::Arc<crate::RouteRuntimeState>,
prepared: crate::PreparedRouteStart,
) -> ksp_core_lib::Result<crate::RouteRuntimeLaunch> {
let reservation = runtime_state.reserve(&prepared);
let reservation = match reservation {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let store = ksp_store_lib::Store::open(prepared.store_settings).await;
let store = match store {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
runtime_state.rollback(reservation.token);
return std::result::Result::Err(
ksp_core_lib::Error::new(crate::ERROR_CODE_ROUTE_RUNTIME_START_FAILED, "Cannot open Raw Transaction Ingest Desk Store runtime")
.with_source(error),
);
},
};
let health = store.health().await;
if health.state() != ksp_store_lib::StoreHealthState::Ready {
let close = store.close().await;
runtime_state.rollback(reservation.token);
if let std::result::Result::Err(error) = close {
return std::result::Result::Err(
ksp_core_lib::Error::new(
crate::ERROR_CODE_ROUTE_RUNTIME_STORE_SHUTDOWN_FAILED,
"Cannot close non-ready Raw Transaction Ingest Desk Store runtime",
)
.with_source(error),
);
}
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_ROUTE_RUNTIME_STORE_NOT_READY,
"Raw Transaction Ingest Desk Store readiness probe did not prove Ready",
));
}
let network = store.runtime_snapshot().network().clone();
let settings = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSettings::with_defaults(network, reservation.worker_id.clone());
let store = std::sync::Arc::new(store);
let handle = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestWorker::start_with_runtime_resources(
settings,
std::sync::Arc::clone(&store),
prepared.runtime_resources,
);
let handle = match handle {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
let close = close_store_arc(store).await;
runtime_state.rollback(reservation.token);
if let std::result::Result::Err(close_error) = close {
return std::result::Result::Err(close_error);
}
return std::result::Result::Err(
ksp_core_lib::Error::new(crate::ERROR_CODE_ROUTE_RUNTIME_START_FAILED, "Cannot start Raw Transaction Ingest Desk Worker runtime")
.with_source(error),
);
},
};
let acknowledgement = runtime_state.activate(&reservation, handle.clone());
let acknowledgement = match acknowledgement {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
let _stop_requested = handle.request_stop();
let _terminal = handle.wait_terminal().await;
let close = close_store_arc(store).await;
runtime_state.rollback(reservation.token);
if let std::result::Result::Err(close_error) = close {
return std::result::Result::Err(close_error);
}
return std::result::Result::Err(error);
},
};
ksp_logging_lib::info!(
target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_ROUTE_RUNTIME,
profile_id = reservation.identity.profile_id.as_str(),
network = reservation.identity.network.as_str(),
route_id = reservation.identity.route_id.as_str(),
commitment = reservation.identity.commitment.as_str(),
"Raw Transaction Ingest Desk mono-route Worker handle installed"
);
return std::result::Result::Ok(crate::RouteRuntimeLaunch { acknowledgement, handle, runtime_state, store, token: reservation.token });
}
#[derive(Clone)]
struct RouteRuntimeIdentity {
commitment: crate::RawIngestCommitment,
inventory_generation: u32,
network: String,
profile_id: String,
route_id: crate::RawIngestRouteId,
}
impl RouteRuntimeIdentity {
fn dto(&self, state: crate::RawIngestRouteState) -> crate::RawIngestRouteRuntimeDto {
return crate::RawIngestRouteRuntimeDto {
commitment: self.commitment,
inventory_generation: self.inventory_generation,
network: self.network.clone(),
profile_id: self.profile_id.clone(),
route_id: self.route_id,
state,
};
}
}
struct RouteRuntimeReservation {
identity: RouteRuntimeIdentity,
token: u64,
worker_id: ksp_worker_api::WorkerId,
}
enum RouteRuntimeSlot {
Idle,
Starting {
token: u64,
},
Active {
handle: ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHandle,
identity: RouteRuntimeIdentity,
token: u64,
},
}
async fn close_store_arc(store: std::sync::Arc<ksp_store_lib::Store>) -> ksp_core_lib::Result<()> {
let started = std::time::Instant::now();
let mut store = store;
loop {
match std::sync::Arc::try_unwrap(store) {
std::result::Result::Ok(value) => {
let close = value.close().await;
return match close {
std::result::Result::Ok(()) => std::result::Result::Ok(()),
std::result::Result::Err(error) => std::result::Result::Err(
ksp_core_lib::Error::new(
crate::ERROR_CODE_ROUTE_RUNTIME_STORE_SHUTDOWN_FAILED,
"Cannot close Raw Transaction Ingest Desk Store runtime",
)
.with_source(error),
),
};
},
std::result::Result::Err(shared) => {
store = shared;
},
}
if started.elapsed() >= STORE_RECLAIM_TIMEOUT {
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_ROUTE_RUNTIME_STORE_SHUTDOWN_FAILED,
"Raw Transaction Ingest Desk Store remained shared after terminal Worker",
));
}
tokio::time::sleep(STORE_RECLAIM_POLL_INTERVAL).await;
}
}
fn project_worker_state(state: ksp_worker_api::WorkerState) -> crate::RawIngestRouteState {
return match state {
ksp_worker_api::WorkerState::Created | ksp_worker_api::WorkerState::Starting => crate::RawIngestRouteState::Starting,
ksp_worker_api::WorkerState::Running => crate::RawIngestRouteState::Running,
ksp_worker_api::WorkerState::Stopping => crate::RawIngestRouteState::Stopping,
ksp_worker_api::WorkerState::Stopped => crate::RawIngestRouteState::Stopped,
ksp_worker_api::WorkerState::Faulted(_) => crate::RawIngestRouteState::Faulted,
_ => crate::RawIngestRouteState::Faulted,
};
}
fn runtime_lock_error() -> ksp_core_lib::Error {
return ksp_core_lib::Error::new(crate::ERROR_CODE_APP_STATE_LOCK_FAILED, "Raw Transaction Ingest Desk mono-route runtime state lock is poisoned");
}
#[cfg(test)]
#[path = "../unit_tests/route_runtime.rs"]
mod tests;

View File

@@ -1,14 +1,32 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/route_start.rs
// version: 1
// version: 2
//! Start-time Config revalidation and Transport-resource reconstruction without Worker launch.
//! Start-time Config revalidation and exact Transport/Store resource reconstruction for route validation and runtime 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(
/// Fully revalidated Start preparation retaining backend-only Store settings and exact Worker runtime resources.
pub(crate) struct PreparedRouteStart {
/// Confirmed/finalized commitment validated by the exact route constructor.
pub(crate) commitment: crate::RawIngestCommitment,
/// Inventory generation proven current while this preparation was built.
pub(crate) inventory_generation: u32,
/// Logical network proven identical across Store and Transport.
pub(crate) network: String,
/// Logical network-profile identifier selected by the operator.
pub(crate) profile_id: String,
/// Logical route selected by the operator.
pub(crate) route_id: crate::RawIngestRouteId,
/// Backend-neutral Store settings resolved from Config and retained only in Rust.
pub(crate) store_settings: ksp_store_lib::StoreSettings,
/// Exact single-route Worker runtime resources reconstructed from typed Transport contracts.
pub(crate) runtime_resources: ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources,
}
/// Revalidates one Start request and retains the exact backend resources required for a later Worker launch.
pub(crate) fn prepare_route_start(
management: &ksp_config_lib::ConfigManagement,
current_generation: u32,
request: &crate::RawIngestRouteStartRequestDto,
) -> ksp_core_lib::Result<crate::RawIngestRouteStartValidationDto> {
) -> ksp_core_lib::Result<crate::PreparedRouteStart> {
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,
@@ -78,16 +96,17 @@ pub(crate) fn validate_route_start(
));
}
let commitment = request.commitment.into_transport();
let prepared = match request.route_id {
let runtime_resources = 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);
}
let runtime_resources = match runtime_resources {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => 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,
@@ -96,14 +115,36 @@ pub(crate) fn validate_route_start(
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"
"revalidated Start request and reconstructed exact Store/Transport-owned Worker launch resources"
);
return std::result::Result::Ok(crate::RawIngestRouteStartValidationDto {
return std::result::Result::Ok(crate::PreparedRouteStart {
commitment: request.commitment,
inventory_generation: request.inventory_generation,
network: network.to_owned(),
profile_id: request.profile_id.clone(),
route_id: request.route_id,
store_settings: store.into_settings(),
runtime_resources,
});
}
/// Revalidates one future Start request and destroys the reconstructed resources without opening Store or launching a Worker.
pub(crate) fn validate_route_start(
management: &ksp_config_lib::ConfigManagement,
current_generation: u32,
request: &crate::RawIngestRouteStartRequestDto,
) -> ksp_core_lib::Result<crate::RawIngestRouteStartValidationDto> {
let prepared = crate::prepare_route_start(management, current_generation, request);
let prepared = match prepared {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(crate::RawIngestRouteStartValidationDto {
commitment: prepared.commitment,
inventory_generation: prepared.inventory_generation,
network: prepared.network,
profile_id: prepared.profile_id,
route_id: prepared.route_id,
});
}
@@ -111,7 +152,7 @@ fn prepare_standard_logs(
transport: &ksp_config_lib::ResolvedTransportConfig,
network: &str,
commitment: ksp_onchain_transport_lib::SolanaCommitment,
) -> ksp_core_lib::Result<()> {
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources> {
let endpoint =
single_ws_endpoint(transport, network, ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard, ksp_onchain_transport_lib::WsSubscriptionKind::Logs);
let endpoint = match endpoint {
@@ -139,15 +180,14 @@ fn prepare_standard_logs(
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(());
return std::result::Result::Ok(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::from_standard_logs_source(source));
}
fn prepare_standard_block(
transport: &ksp_config_lib::ResolvedTransportConfig,
network: &str,
commitment: ksp_onchain_transport_lib::SolanaCommitment,
) -> ksp_core_lib::Result<()> {
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources> {
let endpoint =
single_ws_endpoint(transport, network, ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard, ksp_onchain_transport_lib::WsSubscriptionKind::Block);
let endpoint = match endpoint {
@@ -163,8 +203,7 @@ fn prepare_standard_block(
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(());
return std::result::Result::Ok(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::from_standard_block_source(source));
}
fn prepare_helius(
@@ -174,7 +213,7 @@ fn prepare_helius(
base_transport: &ksp_config_lib::ResolvedTransportConfig,
network: &str,
commitment: ksp_onchain_transport_lib::SolanaCommitment,
) -> ksp_core_lib::Result<()> {
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources> {
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,
@@ -214,8 +253,7 @@ fn prepare_helius(
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(());
return std::result::Result::Ok(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::from_helius_transaction_source(source));
}
fn prepare_yellowstone(
@@ -225,7 +263,7 @@ fn prepare_yellowstone(
base_transport: &ksp_config_lib::ResolvedTransportConfig,
network: &str,
commitment: ksp_onchain_transport_lib::SolanaCommitment,
) -> ksp_core_lib::Result<()> {
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources> {
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,
@@ -267,15 +305,14 @@ fn prepare_yellowstone(
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(());
return std::result::Result::Ok(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::new(source));
}
fn prepare_http_polling(
transport: &ksp_config_lib::ResolvedTransportConfig,
network: &str,
commitment: ksp_onchain_transport_lib::SolanaCommitment,
) -> ksp_core_lib::Result<()> {
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources> {
let role = single_http_role(transport.http_settings(), network, &["getSlot", "getBlocksWithLimit", "getBlock"]);
let role = match role {
std::result::Result::Ok(value) => value,
@@ -291,8 +328,7 @@ fn prepare_http_polling(
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(());
return std::result::Result::Ok(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::from_http_block_polling_source(source));
}
fn resolve_optional_transport(

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/tauri.rs
// version: 4
// version: 5
//! Tauri runtime assembly for the KSP Raw Transaction Ingest desktop application.
@@ -80,6 +80,8 @@ fn configure_commands(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tau
get_route_inventory,
get_runtime_status,
splash_frontend_ready,
start_route,
stop_route,
validate_route_start
]);
}
@@ -141,6 +143,35 @@ async fn splash_frontend_ready(
};
}
#[tauri::command]
async fn start_route(
request: crate::RawIngestRouteStartRequestDto,
state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::RawIngestRouteRuntimeDto, crate::CommandErrorDto> {
let launch = state.start_route(&request).await;
let launch = match launch {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(project_command_error("start_route", crate::TRACING_DOMAIN_ROUTE_RUNTIME, &error));
},
};
let acknowledgement = launch.acknowledgement();
let monitor = tauri::async_runtime::spawn(async move {
launch.monitor().await;
});
std::mem::drop(monitor);
return std::result::Result::Ok(acknowledgement);
}
#[tauri::command]
async fn stop_route(state: tauri::State<'_, crate::AppState>) -> std::result::Result<crate::RawIngestRouteRuntimeDto, crate::CommandErrorDto> {
let result = state.stop_route().await;
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("stop_route", crate::TRACING_DOMAIN_ROUTE_RUNTIME, &error)),
};
}
#[tauri::command]
async fn validate_route_start(
request: crate::RawIngestRouteStartRequestDto,

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/tests/dependency_boundary.rs
// version: 3
// version: 4
//! Dependency-boundary canaries for Raw Transaction Ingest Desk Start-resource reconstruction.
@@ -21,30 +21,22 @@ fn read_text(path: &std::path::Path) -> String {
}
#[test]
fn pre_007_manifest_adds_only_worker_vertical_to_existing_config_transport_boundary() {
fn pre_008_manifest_adds_store_facade_and_worker_api_without_physical_backend_edge() {
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",
"ksp-store-lib",
"ksp-worker-api",
"ksp-worker-raw-transaction-ingest-lib",
"tauri-plugin-tracing",
] {
assert!(manifest.contains(required), "missing pre.007 dependency {required}");
assert!(manifest.contains(required), "missing pre.008 dependency {required}");
}
for forbidden in [
"ksp-store-lib",
"ksp-store-api",
"ksp-store-postgres-lib",
"ksp-worker-api",
"ksp-job-backfill-lib",
"reqwest",
"tokio-postgres",
"tonic",
"yellowstone-grpc",
] {
assert!(!manifest.contains(forbidden), "pre.007 opens forbidden direct dependency {forbidden}");
for forbidden in ["ksp-store-api", "ksp-store-postgres-lib", "ksp-job-backfill-lib", "reqwest", "tokio-postgres", "tonic", "yellowstone-grpc"] {
assert!(!manifest.contains(forbidden), "pre.008 opens forbidden direct dependency {forbidden}");
}
}
@@ -74,3 +66,22 @@ fn pre_007_route_start_reconstructs_exact_worker_sources_without_network_store_o
assert!(!source.contains(forbidden), "pre.007 performs premature runtime operation {forbidden}");
}
}
#[test]
fn pre_008_route_runtime_owns_real_store_worker_start_stop_without_physical_backend_types() {
let source = read_text(app_root().join("src/route_runtime.rs").as_path());
for required in [
"Store::open",
"StoreHealthState::Ready",
"RawTransactionIngestWorker::start_with_runtime_resources",
"request_stop",
"wait_terminal",
".close().await",
"Arc::try_unwrap",
] {
assert!(source.contains(required), "missing mono-route runtime operation {required}");
}
for forbidden in ["tokio_postgres", "ksp_store_postgres_lib", "PostgresStore", "connection_uri", "endpoint_url"] {
assert!(!source.contains(forbidden), "pre.008 crosses physical or secret boundary {forbidden}");
}
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/tests/desktop_contract.rs
// version: 4
// version: 5
//! Desktop scaffold contract canaries for Raw Transaction Ingest Desk.
@@ -184,3 +184,19 @@ fn pre_007_start_preflight_is_backend_owned_generation_bound_and_does_not_launch
assert!(!tauri.contains(forbidden), "pre.007 Tauri surface contains premature runtime marker {forbidden}");
}
}
#[test]
fn pre_008_start_stop_commands_and_frontend_controls_are_mono_route_and_backend_owned() {
let tauri = read_text(app_root().join("src/tauri.rs").as_path());
for required in ["start_route", "stop_route", "launch.monitor().await"] {
assert!(tauri.contains(required), "missing pre.008 Tauri runtime marker {required}");
}
let html = read_text(app_root().join("frontend/main.html").as_path());
for required in ["id=\"routeCommitment\"", "id=\"routeRuntimeState\"", "id=\"routeRuntimeIdentity\"", "id=\"stopRoute\""] {
assert!(html.contains(required), "missing pre.008 frontend runtime marker {required}");
}
let main = read_text(app_root().join("frontend/ts/main.ts").as_path());
for required in ["mono-route Start requested", "mono-route Stop requested", "start_route", "stop_route"] {
assert!(main.contains(required), "missing pre.008 frontend control marker {required}");
}
}

View File

@@ -110,3 +110,19 @@ fn pre_007_start_request_and_acknowledgement_are_logical_only_and_resource_free(
assert!(!route_start.contains("with_context(\"endpoint\""));
assert!(!route_start.contains("with_context(\"provider\""));
}
#[test]
fn pre_008_runtime_acknowledgement_and_frontend_tracing_remain_secret_and_handle_free() {
let dto = read_text(app_root().join("src/dto_route.rs").as_path());
assert!(dto.contains("RawIngestRouteRuntimeDto"));
for forbidden in ["worker_id", "worker_handle", "endpoint_url", "connection_uri", "api_key", "source_key"] {
assert!(!dto.contains(forbidden), "runtime DTO exposes forbidden marker {forbidden}");
}
let main = read_text(app_root().join("frontend/ts/main.ts").as_path());
for required in ["profileId", "routeId", "commitment", "state"] {
assert!(main.contains(required), "missing safe runtime trace marker {required}");
}
for forbidden in ["endpointUrl", "connectionUri", "workerId", "sourceKey", "x-token", "rawTransaction"] {
assert!(!main.contains(forbidden), "frontend runtime trace exposes forbidden marker {forbidden}");
}
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/tests/release_completeness.rs
// version: 4
// version: 5
//! Release-completeness canaries for Raw Transaction Ingest Desk Start-resource reconstruction.
@@ -21,7 +21,7 @@ fn read_text(path: &std::path::Path) -> String {
}
#[test]
fn pre_007_production_module_inventory_adds_only_route_start_to_pre_006_surface() {
fn pre_008_production_module_inventory_adds_only_route_runtime_to_pre_007_surface() {
let lib = read_text(app_root().join("src/lib.rs").as_path());
let expected = [
"mod app_state;",
@@ -33,6 +33,7 @@ fn pre_007_production_module_inventory_adds_only_route_start_to_pre_006_surface(
"mod frontend_logging;",
"mod logging_runtime;",
"mod route_inventory;",
"mod route_runtime;",
"mod route_start;",
"mod splash;",
"mod tauri;",
@@ -46,14 +47,14 @@ fn pre_007_production_module_inventory_adds_only_route_start_to_pre_006_surface(
}
#[test]
fn pre_007_public_surface_still_exposes_only_application_run_entry_point() {
fn pre_008_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_007_reconstructs_all_five_worker_source_contracts_without_advancing_store_or_worker_lifecycle() {
fn pre_008_keeps_five_exact_source_reconstructions_separate_from_store_worker_lifecycle() {
let route_start = read_text(app_root().join("src/route_start.rs").as_path());
for required in [
"RawTransactionIngestYellowstoneSource::new",
@@ -61,14 +62,16 @@ fn pre_007_reconstructs_all_five_worker_source_contracts_without_advancing_store
"RawTransactionIngestStandardBlockSource::new",
"RawTransactionIngestHeliusTransactionSource::new",
"RawTransactionIngestHttpBlockPollingSource::new",
"SolanaLogsSubscribeFilter::AllWithVotes",
"SolanaBlockSubscribeFilter::All",
"YellowstoneSubscribeTransactionFilter::new",
"RawTransactionIngestRuntimeResources",
] {
assert!(route_start.contains(required), "missing pre.007 exact Worker resource marker {required}");
assert!(route_start.contains(required), "missing pre.008 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}");
for forbidden in ["Store::open", "start_with_runtime_resources", "request_stop", "wait_terminal"] {
assert!(!route_start.contains(forbidden), "route preparation owns runtime lifecycle marker {forbidden}");
}
let runtime = read_text(app_root().join("src/route_runtime.rs").as_path());
for required in ["Store::open", "start_with_runtime_resources", "request_stop", "wait_terminal", "StoreHealthState::Ready", "close_store_arc"] {
assert!(runtime.contains(required), "missing pre.008 lifecycle marker {required}");
}
}
@@ -87,3 +90,17 @@ fn pre_007_start_revalidation_reuses_inventory_and_requires_profile_network_iden
assert!(route_start.contains(required), "missing pre.007 revalidation marker {required}");
}
}
#[test]
fn pre_008_mono_route_slot_blocks_second_start_until_terminal_store_cleanup() {
let runtime = read_text(app_root().join("src/route_runtime.rs").as_path());
for required in [
"RouteRuntimeSlot::Starting",
"RouteRuntimeSlot::Active",
"ERROR_CODE_ROUTE_RUNTIME_ACTIVE",
"runtime_state.finish(self.token)",
"Arc::try_unwrap",
] {
assert!(runtime.contains(required), "missing mono-route ownership marker {required}");
}
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/unit_tests/dto_route.rs
// version: 2
// version: 3
#[test]
fn pre_005_route_ids_are_exact_and_stably_ordered() {
@@ -58,3 +58,34 @@ fn pre_006_unavailable_reason_codes_are_exact_and_safe() {
);
}
}
#[test]
fn pre_008_runtime_acknowledgement_serializes_only_safe_logical_identity_and_state() {
let dto = crate::RawIngestRouteRuntimeDto {
commitment: crate::RawIngestCommitment::Confirmed,
inventory_generation: 7,
network: "mainnet".to_owned(),
profile_id: "mainnet".to_owned(),
route_id: crate::RawIngestRouteId::StandardBlockDirect,
state: crate::RawIngestRouteState::Running,
};
let value = serde_json::to_value(dto);
assert!(value.is_ok());
if let std::result::Result::Ok(value) = value {
assert_eq!(
value,
serde_json::json!({
"commitment": "confirmed",
"inventoryGeneration": 7,
"network": "mainnet",
"profileId": "mainnet",
"routeId": "standard-block-direct",
"state": "running"
})
);
let encoded = value.to_string();
for forbidden in ["endpoint", "provider", "connectionUri", "sourceKey", "workerId", "payload"] {
assert!(!encoded.contains(forbidden));
}
}
}

View File

@@ -0,0 +1,12 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/unit_tests/route_runtime.rs
// version: 1
#[test]
fn pre_008_mono_route_runtime_starts_idle_without_worker_or_store_material() {
let state = crate::RouteRuntimeState::new();
let idle = state.is_idle();
assert!(idle.is_ok());
if let std::result::Result::Ok(idle) = idle {
assert!(idle);
}
}