v0.3.15-pre.008
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 608
|
# version: 609
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
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"]
|
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]
|
[workspace.package]
|
||||||
version = "0.3.15-pre.7.fix.2"
|
version = "0.3.15-pre.8"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# file: crates/ksp-app-raw-transaction-ingest-desk/Cargo.toml
|
# file: crates/ksp-app-raw-transaction-ingest-desk/Cargo.toml
|
||||||
# version: 3
|
# version: 4
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "ksp-app-raw-transaction-ingest-desk"
|
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-core-lib = { path = "../ksp-core-lib" }
|
||||||
ksp-logging-lib = { path = "../ksp-logging-lib" }
|
ksp-logging-lib = { path = "../ksp-logging-lib" }
|
||||||
ksp-onchain-transport-lib = { path = "../ksp-onchain-transport-lib" }
|
ksp-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" }
|
ksp-worker-raw-transaction-ingest-lib = { path = "../ksp-worker-raw-transaction-ingest-lib" }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
tauri.workspace = true
|
tauri.workspace = true
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: crates/ksp-app-raw-transaction-ingest-desk/README.md -->
|
<!-- file: crates/ksp-app-raw-transaction-ingest-desk/README.md -->
|
||||||
<!-- version: 6 -->
|
<!-- version: 7 -->
|
||||||
|
|
||||||
# `ksp-app-raw-transaction-ingest-desk`
|
# `ksp-app-raw-transaction-ingest-desk`
|
||||||
|
|
||||||
@@ -20,14 +20,16 @@ bin : ksp-app-raw-transaction-ingest-desk
|
|||||||
```text
|
```text
|
||||||
Config -> composite, profils, secrets et résolution effective
|
Config -> composite, profils, secrets et résolution effective
|
||||||
Transport -> capabilities HTTP/WS/gRPC et ressources physiques
|
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
|
Store -> persistance via la façade KSP
|
||||||
Worker -> validation des contrats source, 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.
|
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.
|
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 n’est 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 n’est pas présentée sur Testnet tant qu’Helius ne fournit pas de source Testnet configurée.
|
Une route explicitement liée à un provider n’est 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 n’est pas présentée sur Testnet tant qu’Helius 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.
|
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
|
## Ports de développement
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: crates/ksp-app-raw-transaction-ingest-desk/USAGE.md -->
|
<!-- file: crates/ksp-app-raw-transaction-ingest-desk/USAGE.md -->
|
||||||
<!-- version: 5 -->
|
<!-- version: 6 -->
|
||||||
|
|
||||||
# Utilisation de `ksp-app-raw-transaction-ingest-desk`
|
# 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
|
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.
|
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 n’est déclarée pour le réseau n’est 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 lorsqu’aucune source Helius Testnet n’est déclarée.
|
Une route provider-specific dont aucune source n’est déclarée pour le réseau n’est 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 lorsqu’aucune source Helius Testnet n’est déclarée.
|
||||||
|
|
||||||
Les URLs, tokens, metadata secrètes, handles Transport/Store/Worker, URI Store, source keys et payloads RAW restent backend-only.
|
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é
|
## 3. Raisons d'indisponibilité
|
||||||
|
|
||||||
Les reasons exposées sont des codes applicatifs bornés, par exemple :
|
Les reasons exposées sont des codes applicatifs bornés, par exemple :
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: crates/ksp-app-raw-transaction-ingest-desk/frontend/main.html -->
|
<!-- file: crates/ksp-app-raw-transaction-ingest-desk/frontend/main.html -->
|
||||||
<!-- version: 4 -->
|
<!-- version: 5 -->
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="fr">
|
<html lang="fr">
|
||||||
|
|
||||||
@@ -51,18 +51,25 @@
|
|||||||
<div class="d-flex align-items-start justify-content-between gap-3 flex-wrap mb-4">
|
<div class="d-flex align-items-start justify-content-between gap-3 flex-wrap mb-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="h3 mb-1">Routes live</h1>
|
<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>
|
</div>
|
||||||
<button id="refreshRouteInventory" class="btn btn-outline-primary btn-sm" type="button">
|
<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
|
<i class="fa-solid fa-rotate me-2" aria-hidden="true"></i>Refresh
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="row g-3 align-items-end mb-4">
|
<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>
|
<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>
|
<select id="routeProfile" class="form-select" aria-label="Réseau Raw Transaction Ingest Desk"></select>
|
||||||
</div>
|
</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 class="small text-body-secondary">Réseau</div>
|
||||||
<div id="routeNetwork" class="fw-semibold app-route-code">—</div>
|
<div id="routeNetwork" class="fw-semibold app-route-code">—</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -72,7 +79,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="alert alert-info" role="status">
|
<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>
|
||||||
<div class="small text-body-secondary mb-3">Réseau affiché : <span id="routeSelectedProfile" class="app-route-code">—</span></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>
|
<div id="routeFoundation" class="row g-3 app-route-grid" aria-live="polite"></div>
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
// file: crates/ksp-app-raw-transaction-ingest-desk/frontend/ts/main.ts
|
// file: crates/ksp-app-raw-transaction-ingest-desk/frontend/ts/main.ts
|
||||||
// version: 4
|
// version: 5
|
||||||
|
|
||||||
import "bootstrap";
|
import "bootstrap";
|
||||||
import ResizeObserver from "resize-observer-polyfill";
|
import ResizeObserver from "resize-observer-polyfill";
|
||||||
import "simplebar";
|
import "simplebar";
|
||||||
import type { ShellStatusDto } from "./bindings/ksp_app_raw_transaction_ingest_desk/dto_common/ShellStatusDto.ts";
|
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 { 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 { frontendDebug, frontendInfo, frontendTrace, frontendWarn, installFrontendConsoleBridge } from "./frontend_log";
|
||||||
import { invokeKsp } from "./invoke";
|
import { invokeKsp } from "./invoke";
|
||||||
|
|
||||||
@@ -19,6 +23,8 @@ const viewTitles: Record<ViewId, string> = {
|
|||||||
diagnostics: "Diagnostics",
|
diagnostics: "Diagnostics",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let activeRuntime: RawIngestRouteRuntimeDto | null = null;
|
||||||
|
let lastRuntime: RawIngestRouteRuntimeDto | null = null;
|
||||||
let routeInventory: RawIngestRouteInventoryDto | null = null;
|
let routeInventory: RawIngestRouteInventoryDto | null = null;
|
||||||
let selectedProfileId: string | null = null;
|
let selectedProfileId: string | null = null;
|
||||||
|
|
||||||
@@ -137,6 +143,21 @@ function renderSelectedProfile(): void {
|
|||||||
reason.textContent = reasonLabel(route.reason);
|
reason.textContent = reasonLabel(route.reason);
|
||||||
meta.append(state, network, reason);
|
meta.append(state, network, reason);
|
||||||
body.append(title, code, meta);
|
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);
|
card.append(body);
|
||||||
column.append(card);
|
column.append(card);
|
||||||
routeRoot.append(column);
|
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 {
|
function renderRouteInventory(inventory: RawIngestRouteInventoryDto): void {
|
||||||
routeInventory = inventory;
|
routeInventory = inventory;
|
||||||
const requestedProfile = selectedProfileId;
|
const requestedProfile = selectedProfileId;
|
||||||
@@ -169,6 +271,7 @@ function renderRouteInventory(inventory: RawIngestRouteInventoryDto): void {
|
|||||||
text("#runtimeInventoryGeneration", inventory.generation.toString());
|
text("#runtimeInventoryGeneration", inventory.generation.toString());
|
||||||
text("#runtimeInventoryDefaultProfile", inventory.defaultProfile);
|
text("#runtimeInventoryDefaultProfile", inventory.defaultProfile);
|
||||||
renderSelectedProfile();
|
renderSelectedProfile();
|
||||||
|
renderMonoRouteRuntime();
|
||||||
frontendTrace("main", "Raw Transaction Ingest Desk Config route inventory rendered", {
|
frontendTrace("main", "Raw Transaction Ingest Desk Config route inventory rendered", {
|
||||||
generation: inventory.generation,
|
generation: inventory.generation,
|
||||||
profileCount: inventory.profiles.length,
|
profileCount: inventory.profiles.length,
|
||||||
@@ -220,6 +323,9 @@ function bindRefreshControls(): void {
|
|||||||
document.querySelector<HTMLButtonElement>("#refreshRuntimeStatus")?.addEventListener("click", () => {
|
document.querySelector<HTMLButtonElement>("#refreshRuntimeStatus")?.addEventListener("click", () => {
|
||||||
void refreshRuntimeStatus().catch(() => frontendWarn("main", "Raw Transaction Ingest Desk runtime status refresh failed"));
|
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 => {
|
document.querySelector<HTMLSelectElement>("#routeProfile")?.addEventListener("change", event => {
|
||||||
const selector = event.currentTarget;
|
const selector = event.currentTarget;
|
||||||
if (!(selector instanceof HTMLSelectElement)) {
|
if (!(selector instanceof HTMLSelectElement)) {
|
||||||
@@ -239,7 +345,7 @@ async function initializeMain(): Promise<void> {
|
|||||||
activateView("routes", "startup");
|
activateView("routes", "startup");
|
||||||
await refreshRouteInventory();
|
await refreshRouteInventory();
|
||||||
await refreshRuntimeStatus();
|
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"));
|
void initializeMain().catch(() => frontendWarn("main", "Raw Transaction Ingest Desk frontend initialization failed"));
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/app_state.rs
|
// 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.
|
//! 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,
|
config_management: ksp_config_lib::ConfigManagement,
|
||||||
inventory_generation: std::sync::Mutex<u32>,
|
inventory_generation: std::sync::Mutex<u32>,
|
||||||
logging_runtime: std::sync::Mutex<LoggingRuntimeState>,
|
logging_runtime: std::sync::Mutex<LoggingRuntimeState>,
|
||||||
|
route_runtime: std::sync::Arc<crate::RouteRuntimeState>,
|
||||||
splash_settings: crate::SplashSettings,
|
splash_settings: crate::SplashSettings,
|
||||||
splash_sequence_started: std::sync::atomic::AtomicBool,
|
splash_sequence_started: std::sync::atomic::AtomicBool,
|
||||||
}
|
}
|
||||||
@@ -55,6 +56,7 @@ impl crate::AppState {
|
|||||||
fallback_active: logging_startup.fallback_active,
|
fallback_active: logging_startup.fallback_active,
|
||||||
startup_diagnostic: logging_startup.startup_diagnostic,
|
startup_diagnostic: logging_startup.startup_diagnostic,
|
||||||
}),
|
}),
|
||||||
|
route_runtime: std::sync::Arc::new(crate::RouteRuntimeState::new()),
|
||||||
splash_settings,
|
splash_settings,
|
||||||
splash_sequence_started: std::sync::atomic::AtomicBool::new(false),
|
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);
|
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.
|
/// Builds the safe scaffold status exposed by the shell.
|
||||||
pub(crate) fn shell_status(&self) -> ksp_core_lib::Result<crate::ShellStatusDto> {
|
pub(crate) fn shell_status(&self) -> ksp_core_lib::Result<crate::ShellStatusDto> {
|
||||||
let document_count = self.config_management.engine().registry().descriptors().count();
|
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(),
|
application_version: env!("CARGO_PKG_VERSION").to_owned(),
|
||||||
config_document_count: document_count,
|
config_document_count: document_count,
|
||||||
fallback_logging_active: runtime.fallback_active,
|
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(),
|
startup_diagnostic: runtime.startup_diagnostic.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/constants.rs
|
// file: crates/ksp-app-raw-transaction-ingest-desk/src/constants.rs
|
||||||
// version: 4
|
// version: 5
|
||||||
|
|
||||||
//! Application-owned tracing targets and Config component identifiers.
|
//! 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";
|
pub(crate) const TRACING_DOMAIN_FRONTEND: &str = "frontend";
|
||||||
/// Structured domain used while rebuilding Config-only route inventory.
|
/// Structured domain used while rebuilding Config-only route inventory.
|
||||||
pub(crate) const TRACING_DOMAIN_INVENTORY: &str = "raw_transaction_ingest.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.
|
/// 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";
|
pub(crate) const TRACING_DOMAIN_ROUTE_START: &str = "raw_transaction_ingest.route_start";
|
||||||
/// Structured domain used by the Raw Transaction Ingest Desk shell.
|
/// Structured domain used by the Raw Transaction Ingest Desk shell.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/dto_route.rs
|
// 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.
|
//! 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,
|
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.
|
/// Stable application-owned route families used only for safe presentation and orchestration.
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, TS)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, TS)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/errors.rs
|
// file: crates/ksp-app-raw-transaction-ingest-desk/src/errors.rs
|
||||||
// version: 3
|
// version: 4
|
||||||
|
|
||||||
//! Stable Raw Transaction Ingest Desk application error codes.
|
//! 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.
|
/// Exact Start-time Transport resources cannot be reconstructed safely.
|
||||||
pub(crate) const ERROR_CODE_ROUTE_START_PREPARATION_FAILED: ksp_core_lib::ErrorCode =
|
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");
|
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.
|
/// Start request references an inventory generation that is no longer current.
|
||||||
pub(crate) const ERROR_CODE_ROUTE_START_STALE_INVENTORY: ksp_core_lib::ErrorCode =
|
pub(crate) const ERROR_CODE_ROUTE_START_STALE_INVENTORY: ksp_core_lib::ErrorCode =
|
||||||
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "stale_inventory");
|
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "stale_inventory");
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/lib.rs
|
// 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.
|
//! Tauri desktop application scaffold for composing and supervising KSP live RAW transaction ingest routes.
|
||||||
|
|
||||||
@@ -16,6 +16,7 @@ mod errors;
|
|||||||
mod frontend_logging;
|
mod frontend_logging;
|
||||||
mod logging_runtime;
|
mod logging_runtime;
|
||||||
mod route_inventory;
|
mod route_inventory;
|
||||||
|
mod route_runtime;
|
||||||
mod route_start;
|
mod route_start;
|
||||||
mod splash;
|
mod splash;
|
||||||
mod tauri;
|
mod tauri;
|
||||||
@@ -57,6 +58,8 @@ pub(crate) use self::constants::TRACING_DOMAIN_BOOTSTRAP;
|
|||||||
pub(crate) use self::constants::TRACING_DOMAIN_FRONTEND;
|
pub(crate) use self::constants::TRACING_DOMAIN_FRONTEND;
|
||||||
/// Structured domain used while rebuilding Config-only route inventory.
|
/// Structured domain used while rebuilding Config-only route inventory.
|
||||||
pub(crate) use self::constants::TRACING_DOMAIN_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.
|
/// Structured domain used while revalidating future Start requests.
|
||||||
pub(crate) use self::constants::TRACING_DOMAIN_ROUTE_START;
|
pub(crate) use self::constants::TRACING_DOMAIN_ROUTE_START;
|
||||||
/// Structured domain used by the Raw Transaction Ingest Desk shell.
|
/// 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;
|
pub(crate) use self::dto_route::RawIngestRouteId;
|
||||||
/// Complete deterministic Config-only route inventory.
|
/// Complete deterministic Config-only route inventory.
|
||||||
pub(crate) use self::dto_route::RawIngestRouteInventoryDto;
|
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.
|
/// Safe future Start request containing only logical route selection and inventory generation.
|
||||||
pub(crate) use self::dto_route::RawIngestRouteStartRequestDto;
|
pub(crate) use self::dto_route::RawIngestRouteStartRequestDto;
|
||||||
/// Safe acknowledgement that exact Start-time resources were reconstructed without Worker launch.
|
/// 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;
|
pub(crate) use self::errors::ERROR_CODE_LOGGING_BOOTSTRAP_FAILED;
|
||||||
/// Config-only route inventory cannot be projected safely.
|
/// Config-only route inventory cannot be projected safely.
|
||||||
pub(crate) use self::errors::ERROR_CODE_ROUTE_INVENTORY_INVALID;
|
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.
|
/// Exact Start-time Transport resources cannot be reconstructed safely.
|
||||||
pub(crate) use self::errors::ERROR_CODE_ROUTE_START_PREPARATION_FAILED;
|
pub(crate) use self::errors::ERROR_CODE_ROUTE_START_PREPARATION_FAILED;
|
||||||
/// Start request references a stale inventory generation.
|
/// 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;
|
pub(crate) use self::route_inventory::build_route_inventory;
|
||||||
/// Rebuilds the route inventory from one already captured Config environment snapshot.
|
/// Rebuilds the route inventory from one already captured Config environment snapshot.
|
||||||
pub(crate) use self::route_inventory::build_route_inventory_with_environment;
|
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.
|
/// 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;
|
pub(crate) use self::route_start::validate_route_start;
|
||||||
/// Command emitted by Rust to the splash frontend.
|
/// Command emitted by Rust to the splash frontend.
|
||||||
|
|||||||
396
crates/ksp-app-raw-transaction-ingest-desk/src/route_runtime.rs
Normal file
396
crates/ksp-app-raw-transaction-ingest-desk/src/route_runtime.rs
Normal 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;
|
||||||
@@ -1,14 +1,32 @@
|
|||||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/route_start.rs
|
// 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.
|
/// Fully revalidated Start preparation retaining backend-only Store settings and exact Worker runtime resources.
|
||||||
pub(crate) fn validate_route_start(
|
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,
|
management: &ksp_config_lib::ConfigManagement,
|
||||||
current_generation: u32,
|
current_generation: u32,
|
||||||
request: &crate::RawIngestRouteStartRequestDto,
|
request: &crate::RawIngestRouteStartRequestDto,
|
||||||
) -> ksp_core_lib::Result<crate::RawIngestRouteStartValidationDto> {
|
) -> ksp_core_lib::Result<crate::PreparedRouteStart> {
|
||||||
if current_generation == 0 || request.inventory_generation != current_generation {
|
if current_generation == 0 || request.inventory_generation != current_generation {
|
||||||
return std::result::Result::Err(ksp_core_lib::Error::new(
|
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||||
crate::ERROR_CODE_ROUTE_START_STALE_INVENTORY,
|
crate::ERROR_CODE_ROUTE_START_STALE_INVENTORY,
|
||||||
@@ -78,16 +96,17 @@ pub(crate) fn validate_route_start(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
let commitment = request.commitment.into_transport();
|
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::YellowstoneHydrated => prepare_yellowstone(management, &environment, &composite, &transport, network, commitment),
|
||||||
crate::RawIngestRouteId::StandardLogsHydrated => prepare_standard_logs(&transport, network, commitment),
|
crate::RawIngestRouteId::StandardLogsHydrated => prepare_standard_logs(&transport, network, commitment),
|
||||||
crate::RawIngestRouteId::StandardBlockDirect => prepare_standard_block(&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::HeliusTransactionHydrated => prepare_helius(management, &environment, &composite, &transport, network, commitment),
|
||||||
crate::RawIngestRouteId::HttpBlockPolling => prepare_http_polling(&transport, network, commitment),
|
crate::RawIngestRouteId::HttpBlockPolling => prepare_http_polling(&transport, network, commitment),
|
||||||
};
|
};
|
||||||
if let std::result::Result::Err(error) = prepared {
|
let runtime_resources = match runtime_resources {
|
||||||
return route_start_error("Cannot reconstruct Start-time Transport resources", error);
|
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!(
|
ksp_logging_lib::debug!(
|
||||||
target: crate::TRACING_TARGET,
|
target: crate::TRACING_TARGET,
|
||||||
domain = crate::TRACING_DOMAIN_ROUTE_START,
|
domain = crate::TRACING_DOMAIN_ROUTE_START,
|
||||||
@@ -96,14 +115,36 @@ pub(crate) fn validate_route_start(
|
|||||||
route_id = request.route_id.as_str(),
|
route_id = request.route_id.as_str(),
|
||||||
inventory_generation = request.inventory_generation,
|
inventory_generation = request.inventory_generation,
|
||||||
commitment = request.commitment.as_str(),
|
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,
|
commitment: request.commitment,
|
||||||
inventory_generation: request.inventory_generation,
|
inventory_generation: request.inventory_generation,
|
||||||
network: network.to_owned(),
|
network: network.to_owned(),
|
||||||
profile_id: request.profile_id.clone(),
|
profile_id: request.profile_id.clone(),
|
||||||
route_id: request.route_id,
|
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,
|
transport: &ksp_config_lib::ResolvedTransportConfig,
|
||||||
network: &str,
|
network: &str,
|
||||||
commitment: ksp_onchain_transport_lib::SolanaCommitment,
|
commitment: ksp_onchain_transport_lib::SolanaCommitment,
|
||||||
) -> ksp_core_lib::Result<()> {
|
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources> {
|
||||||
let endpoint =
|
let endpoint =
|
||||||
single_ws_endpoint(transport, network, ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard, ksp_onchain_transport_lib::WsSubscriptionKind::Logs);
|
single_ws_endpoint(transport, network, ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard, ksp_onchain_transport_lib::WsSubscriptionKind::Logs);
|
||||||
let endpoint = match endpoint {
|
let endpoint = match endpoint {
|
||||||
@@ -139,15 +180,14 @@ fn prepare_standard_logs(
|
|||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
std::mem::drop(source);
|
return std::result::Result::Ok(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::from_standard_logs_source(source));
|
||||||
return std::result::Result::Ok(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_standard_block(
|
fn prepare_standard_block(
|
||||||
transport: &ksp_config_lib::ResolvedTransportConfig,
|
transport: &ksp_config_lib::ResolvedTransportConfig,
|
||||||
network: &str,
|
network: &str,
|
||||||
commitment: ksp_onchain_transport_lib::SolanaCommitment,
|
commitment: ksp_onchain_transport_lib::SolanaCommitment,
|
||||||
) -> ksp_core_lib::Result<()> {
|
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources> {
|
||||||
let endpoint =
|
let endpoint =
|
||||||
single_ws_endpoint(transport, network, ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard, ksp_onchain_transport_lib::WsSubscriptionKind::Block);
|
single_ws_endpoint(transport, network, ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard, ksp_onchain_transport_lib::WsSubscriptionKind::Block);
|
||||||
let endpoint = match endpoint {
|
let endpoint = match endpoint {
|
||||||
@@ -163,8 +203,7 @@ fn prepare_standard_block(
|
|||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
std::mem::drop(source);
|
return std::result::Result::Ok(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::from_standard_block_source(source));
|
||||||
return std::result::Result::Ok(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_helius(
|
fn prepare_helius(
|
||||||
@@ -174,7 +213,7 @@ fn prepare_helius(
|
|||||||
base_transport: &ksp_config_lib::ResolvedTransportConfig,
|
base_transport: &ksp_config_lib::ResolvedTransportConfig,
|
||||||
network: &str,
|
network: &str,
|
||||||
commitment: ksp_onchain_transport_lib::SolanaCommitment,
|
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 = resolve_optional_transport(management, environment, composite, crate::COMPOSITE_COMPONENT_ID_TRANSPORT_HELIUS, network);
|
||||||
let transport = match transport {
|
let transport = match transport {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
@@ -214,8 +253,7 @@ fn prepare_helius(
|
|||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
std::mem::drop(source);
|
return std::result::Result::Ok(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::from_helius_transaction_source(source));
|
||||||
return std::result::Result::Ok(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_yellowstone(
|
fn prepare_yellowstone(
|
||||||
@@ -225,7 +263,7 @@ fn prepare_yellowstone(
|
|||||||
base_transport: &ksp_config_lib::ResolvedTransportConfig,
|
base_transport: &ksp_config_lib::ResolvedTransportConfig,
|
||||||
network: &str,
|
network: &str,
|
||||||
commitment: ksp_onchain_transport_lib::SolanaCommitment,
|
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 = resolve_optional_transport(management, environment, composite, crate::COMPOSITE_COMPONENT_ID_TRANSPORT_YELLOWSTONE, network);
|
||||||
let transport = match transport {
|
let transport = match transport {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
@@ -267,15 +305,14 @@ fn prepare_yellowstone(
|
|||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
std::mem::drop(source);
|
return std::result::Result::Ok(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::new(source));
|
||||||
return std::result::Result::Ok(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_http_polling(
|
fn prepare_http_polling(
|
||||||
transport: &ksp_config_lib::ResolvedTransportConfig,
|
transport: &ksp_config_lib::ResolvedTransportConfig,
|
||||||
network: &str,
|
network: &str,
|
||||||
commitment: ksp_onchain_transport_lib::SolanaCommitment,
|
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 = single_http_role(transport.http_settings(), network, &["getSlot", "getBlocksWithLimit", "getBlock"]);
|
||||||
let role = match role {
|
let role = match role {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
@@ -291,8 +328,7 @@ fn prepare_http_polling(
|
|||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
std::mem::drop(source);
|
return std::result::Result::Ok(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::from_http_block_polling_source(source));
|
||||||
return std::result::Result::Ok(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_optional_transport(
|
fn resolve_optional_transport(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/tauri.rs
|
// 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.
|
//! 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_route_inventory,
|
||||||
get_runtime_status,
|
get_runtime_status,
|
||||||
splash_frontend_ready,
|
splash_frontend_ready,
|
||||||
|
start_route,
|
||||||
|
stop_route,
|
||||||
validate_route_start
|
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]
|
#[tauri::command]
|
||||||
async fn validate_route_start(
|
async fn validate_route_start(
|
||||||
request: crate::RawIngestRouteStartRequestDto,
|
request: crate::RawIngestRouteStartRequestDto,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-raw-transaction-ingest-desk/tests/dependency_boundary.rs
|
// 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.
|
//! Dependency-boundary canaries for Raw Transaction Ingest Desk Start-resource reconstruction.
|
||||||
|
|
||||||
@@ -21,30 +21,22 @@ fn read_text(path: &std::path::Path) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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());
|
let manifest = read_text(app_root().join("Cargo.toml").as_path());
|
||||||
for required in [
|
for required in [
|
||||||
"ksp-config-lib",
|
"ksp-config-lib",
|
||||||
"ksp-core-lib",
|
"ksp-core-lib",
|
||||||
"ksp-logging-lib",
|
"ksp-logging-lib",
|
||||||
"ksp-onchain-transport-lib",
|
"ksp-onchain-transport-lib",
|
||||||
|
"ksp-store-lib",
|
||||||
|
"ksp-worker-api",
|
||||||
"ksp-worker-raw-transaction-ingest-lib",
|
"ksp-worker-raw-transaction-ingest-lib",
|
||||||
"tauri-plugin-tracing",
|
"tauri-plugin-tracing",
|
||||||
] {
|
] {
|
||||||
assert!(manifest.contains(required), "missing pre.007 dependency {required}");
|
assert!(manifest.contains(required), "missing pre.008 dependency {required}");
|
||||||
}
|
}
|
||||||
for forbidden in [
|
for forbidden in ["ksp-store-api", "ksp-store-postgres-lib", "ksp-job-backfill-lib", "reqwest", "tokio-postgres", "tonic", "yellowstone-grpc"] {
|
||||||
"ksp-store-lib",
|
assert!(!manifest.contains(forbidden), "pre.008 opens forbidden direct dependency {forbidden}");
|
||||||
"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}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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}");
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-raw-transaction-ingest-desk/tests/desktop_contract.rs
|
// 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.
|
//! 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}");
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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(\"endpoint\""));
|
||||||
assert!(!route_start.contains("with_context(\"provider\""));
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-raw-transaction-ingest-desk/tests/release_completeness.rs
|
// 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.
|
//! Release-completeness canaries for Raw Transaction Ingest Desk Start-resource reconstruction.
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ fn read_text(path: &std::path::Path) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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 lib = read_text(app_root().join("src/lib.rs").as_path());
|
||||||
let expected = [
|
let expected = [
|
||||||
"mod app_state;",
|
"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 frontend_logging;",
|
||||||
"mod logging_runtime;",
|
"mod logging_runtime;",
|
||||||
"mod route_inventory;",
|
"mod route_inventory;",
|
||||||
|
"mod route_runtime;",
|
||||||
"mod route_start;",
|
"mod route_start;",
|
||||||
"mod splash;",
|
"mod splash;",
|
||||||
"mod tauri;",
|
"mod tauri;",
|
||||||
@@ -46,14 +47,14 @@ fn pre_007_production_module_inventory_adds_only_route_start_to_pre_006_surface(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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 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<_>>();
|
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;"]);
|
assert_eq!(public_reexports, vec!["pub use self::tauri::run;"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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());
|
let route_start = read_text(app_root().join("src/route_start.rs").as_path());
|
||||||
for required in [
|
for required in [
|
||||||
"RawTransactionIngestYellowstoneSource::new",
|
"RawTransactionIngestYellowstoneSource::new",
|
||||||
@@ -61,14 +62,16 @@ fn pre_007_reconstructs_all_five_worker_source_contracts_without_advancing_store
|
|||||||
"RawTransactionIngestStandardBlockSource::new",
|
"RawTransactionIngestStandardBlockSource::new",
|
||||||
"RawTransactionIngestHeliusTransactionSource::new",
|
"RawTransactionIngestHeliusTransactionSource::new",
|
||||||
"RawTransactionIngestHttpBlockPollingSource::new",
|
"RawTransactionIngestHttpBlockPollingSource::new",
|
||||||
"SolanaLogsSubscribeFilter::AllWithVotes",
|
"RawTransactionIngestRuntimeResources",
|
||||||
"SolanaBlockSubscribeFilter::All",
|
|
||||||
"YellowstoneSubscribeTransactionFilter::new",
|
|
||||||
] {
|
] {
|
||||||
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"] {
|
for forbidden in ["Store::open", "start_with_runtime_resources", "request_stop", "wait_terminal"] {
|
||||||
assert!(!route_start.contains(forbidden), "pre.007 advanced premature lifecycle marker {forbidden}");
|
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}");
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-raw-transaction-ingest-desk/unit_tests/dto_route.rs
|
// file: crates/ksp-app-raw-transaction-ingest-desk/unit_tests/dto_route.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pre_005_route_ids_are_exact_and_stably_ordered() {
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
146
deltas/0.3.15/pre.008.md
Normal file
146
deltas/0.3.15/pre.008.md
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
<!-- file: deltas/0.3.15/pre.008.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.3.15-pre.008`
|
||||||
|
|
||||||
|
## Base
|
||||||
|
|
||||||
|
```text
|
||||||
|
base archive : ksp-general-0.3.15-pre.007-fix.002.zip
|
||||||
|
base SHA-256 : 8addfa784748c25c8f267020130f0b0d053dbe2192002b50d35775060fff4daa
|
||||||
|
base version : 0.3.15-pre.7.fix.2
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur de la base passe les audits Rust/Markdown, `cargo check --workspace`, Clippy workspace `-D warnings`, les tests Raw Transaction Ingest Desk et le workspace all-targets/all-features. Le lancement final utilise exclusivement `cargo tauri dev`; Tauri déclenche lui-même son `beforeDevCommand` frontend.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Matérialiser le premier runtime réel de `ksp-app-raw-transaction-ingest-desk` : ouverture Store + démarrage/arrêt d'un Worker pour exactement une route logique, sans avancer le multi-route de `pre.009` ni le monitoring continu de `pre.010`.
|
||||||
|
|
||||||
|
## Runtime mono-route
|
||||||
|
|
||||||
|
Le Start conserve l'entrée logique `profile_id + route_id + inventory_generation + commitment` et réutilise intégralement la revalidation `pre.007`.
|
||||||
|
|
||||||
|
```text
|
||||||
|
revalidation Config/Store/Transport
|
||||||
|
-> ressources Worker exactes
|
||||||
|
-> réservation slot mono-route
|
||||||
|
-> Store::open
|
||||||
|
-> Store::health == Ready
|
||||||
|
-> WorkerId backend-owned
|
||||||
|
-> RawTransactionIngestWorker::start_with_runtime_resources
|
||||||
|
-> installation handle
|
||||||
|
-> acknowledgement IPC sûr
|
||||||
|
```
|
||||||
|
|
||||||
|
Le slot est réservé avant l'ouverture Store afin que deux Start concurrents ne puissent jamais ouvrir deux runtimes. Toute erreur avant installation du handle rollbacke la réservation ; un Store déjà ouvert est explicitement fermé.
|
||||||
|
|
||||||
|
Le runtime Worker utilise `RawTransactionIngestSettings::with_defaults` et le réseau porté par le Store réellement ouvert. Aucun `WorkerId`, handle, endpoint, URL, credential, URI Store, source key ou payload RAW ne traverse IPC.
|
||||||
|
|
||||||
|
## Stop et fermeture Store
|
||||||
|
|
||||||
|
`stop_route` demande l'arrêt coopératif avec `request_stop()` puis attend `wait_terminal()`. Un monitor backend, installé dès le Start, attend également le terminal afin de traiter aussi une terminaison spontanée.
|
||||||
|
|
||||||
|
Le Worker détient le Store sous `Arc`. Après publication terminal, le monitor attend de façon bornée que le Worker ait relâché son clone, récupère l'unique Store avec `Arc::try_unwrap`, appelle explicitement `Store::close()` puis seulement libère le slot mono-route. Aucun nouveau Start n'est donc accepté pendant le drain ou la fermeture Store.
|
||||||
|
|
||||||
|
## IPC et frontend
|
||||||
|
|
||||||
|
Nouvelles commandes :
|
||||||
|
|
||||||
|
```text
|
||||||
|
start_route
|
||||||
|
stop_route
|
||||||
|
```
|
||||||
|
|
||||||
|
Le DTO `RawIngestRouteRuntimeDto` contient uniquement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
commitment
|
||||||
|
inventory_generation
|
||||||
|
network
|
||||||
|
profile_id
|
||||||
|
route_id
|
||||||
|
state
|
||||||
|
```
|
||||||
|
|
||||||
|
La vue Routes ajoute le choix `confirmed/finalized`, un bouton Start sur chaque route `Configured`, une projection locale sûre de l'accusé runtime et un bouton Stop. Le frontend trace uniquement les identités logiques sûres et les états. Il ne fait ni réseau direct, ni stockage navigateur, ni accès aux ressources physiques.
|
||||||
|
|
||||||
|
La tranche ne fournit pas encore le flux de snapshots runtime : une terminaison spontanée est nettoyée backend-side, mais sa projection continue vers l'UI reste `pre.010`.
|
||||||
|
|
||||||
|
## Frontières conservées
|
||||||
|
|
||||||
|
```text
|
||||||
|
Config : profils, secrets, composition et résolution effective
|
||||||
|
Transport : endpoints, pools, sessions/channels et capabilities
|
||||||
|
Store : ouverture/health/close via ksp-store-lib uniquement
|
||||||
|
Worker : source runtime, acquisition, lifecycle et Stop coopératif
|
||||||
|
Desk pre.008 : sélection logique + ownership d'un unique runtime actif
|
||||||
|
```
|
||||||
|
|
||||||
|
Sont explicitement hors tranche : plusieurs Workers simultanés, Store partagé inter-Workers, isolation de faults inter-route, snapshot/event bridge, replay/gap UI, shutdown applicatif durci et races hostiles avancées.
|
||||||
|
|
||||||
|
## Version
|
||||||
|
|
||||||
|
```text
|
||||||
|
workspace.package.version : 0.3.15-pre.8
|
||||||
|
root Cargo header counter : 609
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation d'assemblage
|
||||||
|
|
||||||
|
L'environnement d'assemblage ne fournit ni Cargo ni rustfmt ; aucun résultat Cargo local n'est donc revendiqué. Les validations statiques exécutées avant emballage donnent : audits Rust/export/KSP propres, audit Markdown propre (`318` tables / `203` fichiers), parsing HTML/JSON/TOML propre et contrôles de frontières `pre.008` propres.
|
||||||
|
|
||||||
|
Le gate opérateur attendu reste :
|
||||||
|
|
||||||
|
```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 script npm applicatif n'est exécuté directement par l'opérateur.
|
||||||
|
|
||||||
|
## Inventaire exact du delta
|
||||||
|
|
||||||
|
Le delta contient `3` ajouts, `20` modifications et aucune suppression, soit `23` entrées.
|
||||||
|
|
||||||
|
Ajouts :
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-app-raw-transaction-ingest-desk/src/route_runtime.rs
|
||||||
|
crates/ksp-app-raw-transaction-ingest-desk/unit_tests/route_runtime.rs
|
||||||
|
deltas/0.3.15/pre.008.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Modifications :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-app-raw-transaction-ingest-desk/Cargo.toml
|
||||||
|
crates/ksp-app-raw-transaction-ingest-desk/README.md
|
||||||
|
crates/ksp-app-raw-transaction-ingest-desk/USAGE.md
|
||||||
|
crates/ksp-app-raw-transaction-ingest-desk/frontend/main.html
|
||||||
|
crates/ksp-app-raw-transaction-ingest-desk/frontend/ts/main.ts
|
||||||
|
crates/ksp-app-raw-transaction-ingest-desk/src/app_state.rs
|
||||||
|
crates/ksp-app-raw-transaction-ingest-desk/src/constants.rs
|
||||||
|
crates/ksp-app-raw-transaction-ingest-desk/src/dto_route.rs
|
||||||
|
crates/ksp-app-raw-transaction-ingest-desk/src/errors.rs
|
||||||
|
crates/ksp-app-raw-transaction-ingest-desk/src/lib.rs
|
||||||
|
crates/ksp-app-raw-transaction-ingest-desk/src/route_start.rs
|
||||||
|
crates/ksp-app-raw-transaction-ingest-desk/src/tauri.rs
|
||||||
|
crates/ksp-app-raw-transaction-ingest-desk/tests/dependency_boundary.rs
|
||||||
|
crates/ksp-app-raw-transaction-ingest-desk/tests/desktop_contract.rs
|
||||||
|
crates/ksp-app-raw-transaction-ingest-desk/tests/desktop_security.rs
|
||||||
|
crates/ksp-app-raw-transaction-ingest-desk/tests/release_completeness.rs
|
||||||
|
crates/ksp-app-raw-transaction-ingest-desk/unit_tests/dto_route.rs
|
||||||
|
docs/plans/036-V0_3_15_RAW_TRANSACTION_INGEST_DESK_PLAN.md
|
||||||
|
docs/validation/032-V0_3_15_RAW_TRANSACTION_INGEST_DESK.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Suppressions : aucune.
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/plans/036-V0_3_15_RAW_TRANSACTION_INGEST_DESK_PLAN.md -->
|
<!-- file: docs/plans/036-V0_3_15_RAW_TRANSACTION_INGEST_DESK_PLAN.md -->
|
||||||
<!-- version: 10 -->
|
<!-- version: 11 -->
|
||||||
|
|
||||||
# Plan v0.3.15 — Raw Transaction Ingest Desk
|
# Plan v0.3.15 — Raw Transaction Ingest Desk
|
||||||
|
|
||||||
@@ -766,7 +766,30 @@ Le préflight instancie puis détruit immédiatement le contrat source. Il ne fa
|
|||||||
|
|
||||||
### `pre.008` — Store lifecycle et Start/Stop mono-route
|
### `pre.008` — Store lifecycle et Start/Stop mono-route
|
||||||
|
|
||||||
Ouvrir/réutiliser le Store via `ksp-store-lib`, lancer un Worker pour une route, installer le handle avant publication Running, puis fermer proprement Stop/terminal.
|
Ouvrir/réutiliser le Store via `ksp-store-lib`, lancer un Worker pour une route, installer le handle avant toute projection runtime vers le frontend, puis fermer proprement Stop/terminal.
|
||||||
|
|
||||||
|
La tranche retient les invariants suivants :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Start request logique
|
||||||
|
-> revalidation pre.007 + ressources exactes
|
||||||
|
-> réservation atomique du slot mono-route
|
||||||
|
-> Store::open(settings Config)
|
||||||
|
-> Store::health == Ready
|
||||||
|
-> RawTransactionIngestSettings::with_defaults(network, WorkerId backend-owned)
|
||||||
|
-> RawTransactionIngestWorker::start_with_runtime_resources
|
||||||
|
-> installation handle dans le slot
|
||||||
|
-> accusé IPC sûr Starting/Running
|
||||||
|
|
||||||
|
Stop
|
||||||
|
-> handle.request_stop()
|
||||||
|
-> handle.wait_terminal()
|
||||||
|
-> monitor récupère l'unique Arc<Store>
|
||||||
|
-> Store::close()
|
||||||
|
-> libération slot mono-route
|
||||||
|
```
|
||||||
|
|
||||||
|
Le slot reste occupé pendant le drain terminal et la fermeture Store ; aucun second Start n'est admis durant cette fenêtre. Le frontend ne reçoit ni `WorkerId`, ni handle, ni endpoint, ni URI Store. La terminaison spontanée est nettoyée backend-side mais son streaming vers le frontend reste `pre.010`. Le partage d'un même Store entre plusieurs Workers reste `pre.009`.
|
||||||
|
|
||||||
Budget cible : une tranche runtime mono-route.
|
Budget cible : une tranche runtime mono-route.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/validation/032-V0_3_15_RAW_TRANSACTION_INGEST_DESK.md -->
|
<!-- file: docs/validation/032-V0_3_15_RAW_TRANSACTION_INGEST_DESK.md -->
|
||||||
<!-- version: 11 -->
|
<!-- version: 12 -->
|
||||||
|
|
||||||
# Validation v0.3.15 — Raw Transaction Ingest Desk
|
# Validation v0.3.15 — Raw Transaction Ingest Desk
|
||||||
|
|
||||||
@@ -384,7 +384,7 @@ Le catalogue global reste exactement cinq IDs V1, mais l'inventaire d'un profil
|
|||||||
|
|
||||||
### `pre.007` — ressources Start
|
### `pre.007` — ressources Start
|
||||||
|
|
||||||
**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.
|
**Statut : réalisé et gate opérateur PASS après `pre.007-fix.001` et `pre.007-fix.002`.** 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 :
|
La preuve Start-time exige ensuite :
|
||||||
|
|
||||||
@@ -401,9 +401,17 @@ Les cinq constructeurs source Worker sont effectivement réutilisés. `HttpTrans
|
|||||||
|
|
||||||
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.
|
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.007-fix.001` corrige deux erreurs syntaxiques de l'assemblage initial (double visibilité `pub(crate)` et littéraux de canari mal échappés). `pre.007-fix.002` rend le canari de reconstruction hermétique en l'adossant explicitement aux racines Config committées du workspace au lieu des racines par défaut de la machine. Le gate opérateur final de `0.3.15-pre.7.fix.2` passe audits Rust/Markdown, `cargo check --workspace`, Clippy workspace strict, les `34` tests unitaires Raw Ingest Desk, ses suites d'intégration et le workspace `--all-targets --all-features`; `cargo tauri dev` compile et lance ensuite l'application.
|
||||||
|
|
||||||
### `pre.008` — runtime mono-route
|
### `pre.008` — runtime mono-route
|
||||||
|
|
||||||
Store lifecycle + Start/Stop mono-route réel.
|
Implémenté dans l'assemblage de tranche : Start réutilise la revalidation `pre.007`, réserve un slot runtime unique, ouvre le Store exclusivement via `ksp-store-lib`, exige `StoreHealthState::Ready`, construit un `WorkerId` backend-owned puis appelle `RawTransactionIngestWorker::start_with_runtime_resources`. Le handle est installé avant l'accusé IPC.
|
||||||
|
|
||||||
|
Stop demande l'arrêt coopératif au handle et attend son état terminal. Un monitor backend indépendant attend également le terminal, récupère de manière bornée l'unique `Arc<Store>`, appelle explicitement `Store::close()` puis libère le slot. Les échecs avant installation du handle rollbackent le slot et ferment le Store déjà ouvert.
|
||||||
|
|
||||||
|
Surface IPC sûre ajoutée : `start_route` et `stop_route`, avec `RawIngestRouteRuntimeDto` limité à `profile_id`, `network`, `route_id`, `inventory_generation`, `commitment` et `state`. Aucune URL, credential, URI Store, source key, `WorkerId`, handle ou payload RAW n'est projeté.
|
||||||
|
|
||||||
|
Le frontend fournit un contrôle minimal Start/Stop mono-route et le choix `confirmed`/`finalized`. Il ne prétend pas encore fournir le monitoring continu : une terminaison spontanée est nettoyée backend-side mais sa projection temps réel demeure `pre.010`. Multi-route, Store partagé entre Workers et isolation inter-route demeurent `pre.009`.
|
||||||
|
|
||||||
### `pre.009` — runtime multi-route
|
### `pre.009` — runtime multi-route
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user