v0.3.15-pre.006

This commit is contained in:
2026-09-13 11:30:56 +02:00
parent 9ee3cd4a53
commit 0a8ce6e5f9
23 changed files with 1298 additions and 112 deletions

View File

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

View File

@@ -1,11 +1,11 @@
<!-- file: crates/ksp-app-raw-transaction-ingest-desk/README.md -->
<!-- version: 1 -->
<!-- version: 2 -->
# `ksp-app-raw-transaction-ingest-desk`
Application desktop Tauri KSP destinée à composer et superviser les routes live alimentant le Store commun en `RawTransaction`.
Le composant conserve les frontières KSP : Config possède les profils/endpoints/secrets, Transport les clients et sessions, Store la persistance et le Worker la logique d'ingestion. Le shell desktop ne doit jamais déplacer ces responsabilités vers le frontend.
Le composant conserve les frontières KSP : Config possède les profils/endpoints/secrets, Transport les clients et sessions, Store la persistance et le Worker la logique d'ingestion. Le shell desktop ne déplace jamais ces responsabilités vers le frontend.
## Package
@@ -18,14 +18,18 @@ bin : ksp-app-raw-transaction-ingest-desk
## Frontières
```text
Config -> composite + Logging + ressources packagées
Desk -> route IDs/states statiques + Tauri + projections sûres
Transport / Store / Worker -> non construits dans cette tranche
Config -> composite, profils, secrets et résolution effective
Transport -> capabilities HTTP/WS/gRPC et ressources physiques
Desk -> catalogue logique, inventaire Config-only et projections sûres
Store -> persistance via la façade KSP
Worker -> acquisition continue et lifecycle d'exécution
```
Le frontend ne reçoit aucune URL, credential, URI Store, source key ou payload `RawTransaction`.
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.
## Routes V1 réservées
Le frontend ne reçoit aucune URL, credential, URI Store, metadata secrète, source key, handle runtime ou payload `RawTransaction`.
## Routes V1
```text
yellowstone-hydrated
@@ -35,7 +39,7 @@ helius-transaction-hydrated
http-block-polling
```
Leur présence dans l'interface n'est pas une preuve de composabilité : seule la projection backend issue de Config et des préconditions runtime peut déclarer une route disponible.
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`.
## Ports de développement
@@ -50,4 +54,6 @@ Lancement :
(cd crates/ksp-app-raw-transaction-ingest-desk && cargo tauri dev)
```
Le cycle frontend est déclenché par Tauri via ses hooks configurés ; les scripts npm applicatifs ne sont pas lancés directement par l'opérateur.
Voir [`USAGE.md`](USAGE.md) et [`../../docs/plans/036-V0_3_15_RAW_TRANSACTION_INGEST_DESK_PLAN.md`](../../docs/plans/036-V0_3_15_RAW_TRANSACTION_INGEST_DESK_PLAN.md).

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-app-raw-transaction-ingest-desk/USAGE.md -->
<!-- version: 1 -->
<!-- version: 2 -->
# Utilisation de `ksp-app-raw-transaction-ingest-desk`
@@ -11,22 +11,56 @@ Depuis la racine du workspace :
(cd crates/ksp-app-raw-transaction-ingest-desk && cargo tauri dev)
```
Tauri déclenche lui-même les hooks frontend configurés. Les scripts npm applicatifs de développement, contrôle ou build ne sont pas lancés directement par l'opérateur.
Le runtime prépare les ressources Config nécessaires, charge le composite dédié, initialise Logging puis ouvre le shell desktop.
## 2. Vue Routes
La vue Routes présente les routes logiques connues par l'application et leur état applicatif. La présence d'un identifiant de route dans l'interface ne constitue pas, à elle seule, une preuve de composabilité ou de disponibilité runtime ; l'état projeté par le backend reste l'autorité.
La vue Routes charge un inventaire déterministe construit côté Rust depuis le composite Config validé. Le sélecteur présente les profils logiques du composite et la vue projette, pour chaque route V1 :
Les URLs, tokens, metadata secrètes, handles Transport/Store/Worker et payloads RAW restent backend-only.
```text
route_id
famille et label sûrs
réseau logique lorsque résolu
état Config
selectable oui/non
raison sûre lorsque la route est indisponible
génération d'inventaire
```
## 3. Diagnostics
`Configured` signifie uniquement que toutes les requirements Config nécessaires à la route sont cohérentes. Aucun probe réseau, aucune ouverture de Store et aucun Worker ne sont déclenchés par l'inventaire.
La vue Diagnostics expose uniquement des informations techniques sûres nécessaires à l'exploitation du Desk, notamment l'identité de version, l'état du shell, la composition Config active, l'état Logging et les diagnostics projetés par codes/messages sûrs.
Un refresh reconstruit l'inventaire depuis Config et publie une nouvelle génération. La sélection visuelle d'un profil ne choisit aucune URL ou ressource physique et ne démarre aucune acquisition.
Les URLs, tokens, metadata secrètes, handles Transport/Store/Worker, URI Store, source keys et payloads RAW restent backend-only.
## 3. Raisons d'indisponibilité
Les reasons exposées sont des codes applicatifs bornés, par exemple :
```text
profile_unresolved
network_mismatch
missing_http_get_transaction
missing_http_block_scan
missing_ws_logs_capability
missing_ws_block_capability
missing_helius_transaction_capability
missing_yellowstone_grpc
missing_required_secret
```
Le texte arbitraire d'une erreur provider ou d'un secret Config n'est jamais projeté dans l'inventaire.
## 4. Diagnostics
La vue Diagnostics expose uniquement des informations techniques sûres nécessaires à l'exploitation du Desk, notamment l'identité de version, l'état du shell, la composition Config bootstrap, l'état Logging et la génération d'inventaire.
Les diagnostics ne doivent pas exposer de credentials, d'URI Store, d'URL d'endpoint, de source key, de handle runtime ou de payload `RawTransaction`.
## 4. Runtime packagé
## 5. Runtime packagé
Le build Tauri embarque les documents Config et schemas enregistrés. `ksp-config-lib` prépare ensuite la racine KSP user-writable sans embarquer `.env`.
Les fichiers Config et les secrets restent administrés par les mécanismes Config KSP ; l'interface ne doit pas contourner cette ownership.
Les fichiers Config et les secrets restent administrés par les mécanismes Config KSP ; l'interface ne contourne pas cette ownership.

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-app-raw-transaction-ingest-desk/frontend/main.html -->
<!-- version: 2 -->
<!-- version: 3 -->
<!DOCTYPE html>
<html lang="fr">
@@ -21,7 +21,10 @@
<span class="mx-2 fs-5 text-body-secondary" aria-hidden="true"></span>
<span id="headerViewTitle" class="fs-5 text-body text-nowrap">Routes</span>
</div>
<span id="headerPhase" class="badge text-bg-secondary ms-auto">scaffold</span>
<div class="d-flex align-items-center gap-2 ms-auto">
<span id="headerProfile" class="badge text-bg-light border text-dark"></span>
<span id="headerPhase" class="badge text-bg-secondary">inventory</span>
</div>
</div>
</nav>
</header>
@@ -48,29 +51,38 @@
<div class="d-flex align-items-start justify-content-between gap-3 flex-wrap mb-4">
<div>
<h1 class="h3 mb-1">Routes live</h1>
<p class="text-body-secondary mb-0">Fondation statique V1 ; la composabilité Config sera matérialisée en pre.006.</p>
<p class="text-body-secondary mb-0">Inventaire déterministe depuis Config, sans probe réseau ni démarrage de Worker.</p>
</div>
<button id="refreshRouteFoundation" 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
</button>
</div>
<div class="alert alert-info" role="status">
Aucun Worker n'est démarré par ce scaffold. Les cinq IDs ci-dessous ne constituent aucune preuve de disponibilité runtime.
</div>
<div id="routeFoundation" class="row g-3 app-route-grid" aria-live="polite"></div>
<div class="card shadow-sm mt-4">
<div class="card-header fw-semibold">États réservés</div>
<div class="card-body">
<div id="routeStates" class="d-flex flex-wrap gap-2"></div>
<div class="row g-3 align-items-end mb-4">
<div class="col-12 col-md-6">
<label class="form-label" for="routeProfile">Profil applicatif</label>
<select id="routeProfile" class="form-select" aria-label="Profil Raw Transaction Ingest Desk"></select>
</div>
<div class="col-6 col-md-3">
<div class="small text-body-secondary">Réseau</div>
<div id="routeNetwork" class="fw-semibold app-route-code"></div>
</div>
<div class="col-6 col-md-3">
<div class="small text-body-secondary">Génération</div>
<div id="routeInventoryGeneration" class="fw-semibold app-route-code"></div>
</div>
</div>
<div class="alert alert-info" role="status">
<strong>Configured</strong> signifie uniquement que Config prouve la composabilité. Les connexions, le Store et le Worker seront revalidés au Start dans les tranches suivantes.
</div>
<div class="small text-body-secondary mb-3">Profil affiché : <span id="routeSelectedProfile" class="app-route-code"></span></div>
<div id="routeFoundation" class="row g-3 app-route-grid" aria-live="polite"></div>
</section>
<section data-view-panel="diagnostics" hidden>
<div class="d-flex align-items-center justify-content-between gap-3 flex-wrap mb-4">
<div>
<h1 class="h3 mb-1">Diagnostics</h1>
<p class="text-body-secondary mb-0">État sûr du scaffold Config, Logging et desktop.</p>
<p class="text-body-secondary mb-0">État sûr du shell Config, Logging et de la génération d'inventaire.</p>
</div>
<button id="refreshRuntimeStatus" class="btn btn-outline-primary btn-sm" type="button">
<i class="fa-solid fa-rotate me-2" aria-hidden="true"></i>Refresh
@@ -83,9 +95,11 @@
<dt class="col-sm-5">Version</dt><dd id="runtimeVersion" class="col-sm-7"></dd>
<dt class="col-sm-5">Phase</dt><dd id="runtimeShellPhase" class="col-sm-7"></dd>
<dt class="col-sm-5">Documents Config</dt><dd id="runtimeConfigDocuments" class="col-sm-7"></dd>
<dt class="col-sm-5">Profil composite</dt><dd id="runtimeCompositeProfile" class="col-sm-7"></dd>
<dt class="col-sm-5">Profil composite bootstrap</dt><dd id="runtimeCompositeProfile" class="col-sm-7"></dd>
<dt class="col-sm-5">Profil Logging</dt><dd id="runtimeLoggingProfile" class="col-sm-7"></dd>
<dt class="col-sm-5">Fallback Logging</dt><dd id="runtimeLoggingFallback" class="col-sm-7"></dd>
<dt class="col-sm-5">Inventaire default</dt><dd id="runtimeInventoryDefaultProfile" class="col-sm-7"></dd>
<dt class="col-sm-5">Génération inventaire</dt><dd id="runtimeInventoryGeneration" class="col-sm-7"></dd>
</dl>
<div id="runtimeDiagnostic" class="alert alert-warning mt-3 mb-0" role="status" aria-live="polite" hidden></div>
</div>

View File

@@ -1,11 +1,11 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/frontend/ts/main.ts
// version: 2
// version: 3
import "bootstrap";
import ResizeObserver from "resize-observer-polyfill";
import "simplebar";
import type { ShellStatusDto } from "./bindings/ksp_app_raw_transaction_ingest_desk/dto_common/ShellStatusDto.ts";
import type { RawIngestRouteFoundationDto } from "./bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestRouteFoundationDto.ts";
import type { RawIngestRouteInventoryDto } from "./bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestRouteInventoryDto.ts";
import { frontendDebug, frontendInfo, frontendTrace, frontendWarn, installFrontendConsoleBridge } from "./frontend_log";
import { invokeKsp } from "./invoke";
@@ -19,6 +19,9 @@ const viewTitles: Record<ViewId, string> = {
diagnostics: "Diagnostics",
};
let routeInventory: RawIngestRouteInventoryDto | null = null;
let selectedProfileId: string | null = null;
function isViewId(value: string | undefined): value is ViewId {
return value === "routes" || value === "diagnostics";
}
@@ -75,22 +78,40 @@ function bindNavigation(): void {
});
}
function routeLabel(routeId: string): string {
function reasonLabel(reason: string | null): string {
const labels: Record<string, string> = {
"yellowstone-hydrated": "Yellowstone + HTTP hydration",
"standard-logs-hydrated": "Standard Logs + HTTP hydration",
"standard-block-direct": "Standard Block direct",
"helius-transaction-hydrated": "Helius Transaction + HTTP hydration",
"http-block-polling": "HTTP Block Polling",
profile_unresolved: "profil Config non résolu",
network_mismatch: "réseau Transport/Store incohérent",
missing_http_get_transaction: "HTTP getTransaction indisponible",
missing_http_block_scan: "HTTP block scan incomplet",
missing_ws_logs_capability: "capability WS Logs absente",
missing_ws_block_capability: "capability WS Block absente",
missing_helius_transaction_capability: "capability Helius Transaction absente",
missing_yellowstone_grpc: "Yellowstone gRPC absent",
missing_required_secret: "secret Config requis non résolu",
};
return labels[routeId] ?? routeId;
if (reason === null) {
return "composable depuis Config";
}
return labels[reason] ?? reason;
}
function renderRouteFoundation(foundation: RawIngestRouteFoundationDto): void {
function renderSelectedProfile(): void {
if (routeInventory === null) {
return;
}
const profile = routeInventory.profiles.find(candidate => candidate.profileId === selectedProfileId) ?? routeInventory.profiles[0];
if (!profile) {
return;
}
selectedProfileId = profile.profileId;
text("#routeNetwork", profile.network ?? "non résolu");
text("#routeSelectedProfile", profile.profileId);
text("#headerProfile", profile.profileId);
const routeRoot = document.querySelector<HTMLElement>("#routeFoundation");
if (routeRoot) {
routeRoot.replaceChildren();
for (const routeId of foundation.routeIds) {
for (const route of profile.routes) {
const column = document.createElement("div");
column.className = "col-12 col-xl-6 app-route-column";
const card = document.createElement("div");
@@ -99,32 +120,58 @@ function renderRouteFoundation(foundation: RawIngestRouteFoundationDto): void {
body.className = "card-body";
const title = document.createElement("h2");
title.className = "h6 mb-2";
title.textContent = routeLabel(routeId);
title.textContent = route.label;
const code = document.createElement("div");
code.className = "app-route-code text-body-secondary small";
code.textContent = routeId;
const badge = document.createElement("span");
badge.className = "badge text-bg-secondary mt-3";
badge.textContent = "foundation only";
body.append(title, code, badge);
code.textContent = route.routeId;
const meta = document.createElement("div");
meta.className = "d-flex flex-wrap gap-2 mt-3 app-route-meta";
const state = document.createElement("span");
state.className = route.selectable ? "badge text-bg-success" : "badge text-bg-secondary";
state.textContent = route.state;
const network = document.createElement("span");
network.className = "badge text-bg-light border text-dark";
network.textContent = route.network ?? "network unresolved";
const reason = document.createElement("span");
reason.className = route.selectable ? "badge text-bg-light border text-dark" : "badge text-bg-warning";
reason.textContent = reasonLabel(route.reason);
meta.append(state, network, reason);
body.append(title, code, meta);
card.append(body);
column.append(card);
routeRoot.append(column);
}
}
const statesRoot = document.querySelector<HTMLElement>("#routeStates");
if (statesRoot) {
statesRoot.replaceChildren();
for (const state of foundation.states) {
const badge = document.createElement("span");
badge.className = "badge text-bg-light border text-dark";
badge.textContent = state;
statesRoot.append(badge);
frontendTrace("main", "Raw Transaction Ingest Desk Config route inventory profile rendered", {
profileId: profile.profileId,
routeCount: profile.routes.length,
selectableCount: profile.routes.filter(route => route.selectable).length,
});
}
function renderRouteInventory(inventory: RawIngestRouteInventoryDto): void {
routeInventory = inventory;
const requestedProfile = selectedProfileId;
const selectedStillExists = requestedProfile !== null && inventory.profiles.some(profile => profile.profileId === requestedProfile);
selectedProfileId = selectedStillExists ? requestedProfile : inventory.defaultProfile;
const selector = document.querySelector<HTMLSelectElement>("#routeProfile");
if (selector) {
selector.replaceChildren();
for (const profile of inventory.profiles) {
const option = document.createElement("option");
option.value = profile.profileId;
option.textContent = profile.isDefault ? `${profile.profileId} (default)` : profile.profileId;
option.selected = profile.profileId === selectedProfileId;
selector.append(option);
}
}
frontendTrace("main", "Raw Transaction Ingest Desk route foundation rendered", {
routeCount: foundation.routeIds.length,
stateCount: foundation.states.length,
text("#routeInventoryGeneration", inventory.generation.toString());
text("#runtimeInventoryGeneration", inventory.generation.toString());
text("#runtimeInventoryDefaultProfile", inventory.defaultProfile);
renderSelectedProfile();
frontendTrace("main", "Raw Transaction Ingest Desk Config route inventory rendered", {
generation: inventory.generation,
profileCount: inventory.profiles.length,
});
}
@@ -154,10 +201,10 @@ function renderRuntimeStatus(status: ShellStatusDto): void {
});
}
async function refreshRouteFoundation(): Promise<void> {
frontendDebug("main", "Raw Transaction Ingest Desk route foundation refresh requested");
const foundation = await invokeKsp<RawIngestRouteFoundationDto>("main", "get_route_foundation");
renderRouteFoundation(foundation);
async function refreshRouteInventory(): Promise<void> {
frontendDebug("main", "Raw Transaction Ingest Desk Config route inventory refresh requested");
const inventory = await invokeKsp<RawIngestRouteInventoryDto>("main", "get_route_inventory");
renderRouteInventory(inventory);
}
async function refreshRuntimeStatus(): Promise<void> {
@@ -167,12 +214,21 @@ async function refreshRuntimeStatus(): Promise<void> {
}
function bindRefreshControls(): void {
document.querySelector<HTMLButtonElement>("#refreshRouteFoundation")?.addEventListener("click", () => {
void refreshRouteFoundation().catch(() => frontendWarn("main", "Raw Transaction Ingest Desk route foundation refresh failed"));
document.querySelector<HTMLButtonElement>("#refreshRouteInventory")?.addEventListener("click", () => {
void refreshRouteInventory().catch(() => frontendWarn("main", "Raw Transaction Ingest Desk Config route inventory refresh failed"));
});
document.querySelector<HTMLButtonElement>("#refreshRuntimeStatus")?.addEventListener("click", () => {
void refreshRuntimeStatus().catch(() => frontendWarn("main", "Raw Transaction Ingest Desk runtime status refresh failed"));
});
document.querySelector<HTMLSelectElement>("#routeProfile")?.addEventListener("change", event => {
const selector = event.currentTarget;
if (!(selector instanceof HTMLSelectElement)) {
return;
}
selectedProfileId = selector.value;
frontendDebug("main", "Raw Transaction Ingest Desk logical profile selected", { profileId: selectedProfileId });
renderSelectedProfile();
});
}
async function initializeMain(): Promise<void> {
@@ -181,9 +237,9 @@ async function initializeMain(): Promise<void> {
bindNavigation();
bindRefreshControls();
activateView("routes", "startup");
await refreshRouteFoundation();
await refreshRouteInventory();
await refreshRuntimeStatus();
frontendInfo("main", "Raw Transaction Ingest Desk scaffold frontend ready");
frontendInfo("main", "Raw Transaction Ingest Desk Config inventory frontend ready");
}
void initializeMain().catch(() => frontendWarn("main", "Raw Transaction Ingest Desk frontend initialization failed"));

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/app_state.rs
// version: 1
// version: 2
//! Shared backend state owned by the Raw Transaction Ingest Desk Tauri application.
@@ -7,6 +7,7 @@
pub(crate) struct AppState {
active_composite_profile: std::option::Option<String>,
config_management: ksp_config_lib::ConfigManagement,
inventory_generation: std::sync::Mutex<u32>,
logging_runtime: std::sync::Mutex<LoggingRuntimeState>,
splash_settings: crate::SplashSettings,
splash_sequence_started: std::sync::atomic::AtomicBool,
@@ -47,6 +48,7 @@ impl crate::AppState {
return std::result::Result::Ok(Self {
active_composite_profile,
config_management,
inventory_generation: std::sync::Mutex::new(0),
logging_runtime: std::sync::Mutex::new(LoggingRuntimeState {
guard: logging_startup.guard,
active_profile_id: logging_startup.active_profile_id,
@@ -65,6 +67,37 @@ impl crate::AppState {
return crate::RawIngestRouteFoundationDto::scaffold();
}
/// Rebuilds the complete Config-only route inventory and publishes a fresh monotonic generation.
pub(crate) fn route_inventory(&self) -> ksp_core_lib::Result<crate::RawIngestRouteInventoryDto> {
let generation = self.inventory_generation.lock();
let mut 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 next_generation = generation.checked_add(1);
let next_generation = match next_generation {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_ROUTE_INVENTORY_INVALID,
"Raw Transaction Ingest Desk route inventory generation is exhausted",
));
},
};
let inventory = crate::build_route_inventory(&self.config_management, next_generation);
let inventory = match inventory {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
*generation = next_generation;
return std::result::Result::Ok(inventory);
}
/// Builds the safe scaffold status exposed by the shell.
pub(crate) fn shell_status(&self) -> ksp_core_lib::Result<crate::ShellStatusDto> {
let document_count = self.config_management.engine().registry().descriptors().count();
@@ -98,7 +131,7 @@ impl crate::AppState {
application_version: env!("CARGO_PKG_VERSION").to_owned(),
config_document_count: document_count,
fallback_logging_active: runtime.fallback_active,
shell_phase: "pre.005-scaffold".to_owned(),
shell_phase: "pre.006-route-inventory".to_owned(),
startup_diagnostic: runtime.startup_diagnostic.clone(),
});
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/bootstrap.rs
// version: 1
// version: 2
//! Config composite and Logging bootstrap for Raw Transaction Ingest Desk.
@@ -46,13 +46,21 @@ pub(crate) fn config_management(arguments: &[std::ffi::OsString]) -> ksp_core_li
/// Loads the concrete Raw Transaction Ingest Desk Config composite using its registered logical `file_id` and autonomous default profile.
pub(crate) fn load_raw_transaction_ingest_desk_composite(
management: &ksp_config_lib::ConfigManagement,
) -> ksp_core_lib::Result<ksp_config_lib::ResolvedConfigComposite> {
return crate::load_raw_transaction_ingest_desk_composite_profile(management, std::option::Option::None);
}
/// Loads one explicit or default Raw Transaction Ingest Desk composite profile by registered logical file identifier.
pub(crate) fn load_raw_transaction_ingest_desk_composite_profile(
management: &ksp_config_lib::ConfigManagement,
requested_profile: std::option::Option<&str>,
) -> ksp_core_lib::Result<ksp_config_lib::ResolvedConfigComposite> {
let file_id = ksp_config_lib::ConfigFileId::new(ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_RAW_TRANSACTION_INGEST_DESK);
let file_id = match file_id {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return management.engine().load_resolved_composite(&file_id, std::option::Option::None);
return management.engine().load_resolved_composite(&file_id, requested_profile);
}
/// Returns one required standard profile from the Raw Transaction Ingest Desk composite after validating the referenced logical `file_id`.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/constants.rs
// version: 1
// version: 2
//! Application-owned tracing targets and Config component identifiers.
@@ -13,6 +13,8 @@ pub(crate) const COMPOSITE_COMPONENT_ID_TRANSPORT: &str = "transport";
pub(crate) const TRACING_DOMAIN_BOOTSTRAP: &str = "raw_transaction_ingest.bootstrap";
/// Structured domain used by technical frontend events.
pub(crate) const TRACING_DOMAIN_FRONTEND: &str = "frontend";
/// Structured domain used while rebuilding Config-only route inventory.
pub(crate) const TRACING_DOMAIN_INVENTORY: &str = "raw_transaction_ingest.inventory";
/// Structured domain used by the Raw Transaction Ingest Desk shell.
pub(crate) const TRACING_DOMAIN_SHELL: &str = "raw_transaction_ingest.shell";
/// Structured domain used by Tauri window lifecycle operations.

View File

@@ -1,7 +1,7 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/dto_route.rs
// version: 1
// version: 2
//! Safe route-identity and lifecycle-state foundation for Raw Transaction Ingest Desk.
//! Safe route-identity, Config-composability and lifecycle DTOs for Raw Transaction Ingest Desk.
use ts_rs::TS; // rust-rules: trait-import
@@ -28,6 +28,72 @@ impl crate::RawIngestRouteId {
pub(crate) fn all() -> std::vec::Vec<Self> {
return vec![Self::YellowstoneHydrated, Self::StandardLogsHydrated, Self::StandardBlockDirect, Self::HeliusTransactionHydrated, Self::HttpBlockPolling];
}
/// Returns the application-owned route family used by safe UI projections.
#[must_use]
pub(crate) const fn family(self) -> crate::RawIngestRouteFamily {
return match self {
Self::YellowstoneHydrated => crate::RawIngestRouteFamily::Yellowstone,
Self::StandardLogsHydrated => crate::RawIngestRouteFamily::StandardLogs,
Self::StandardBlockDirect => crate::RawIngestRouteFamily::StandardBlock,
Self::HeliusTransactionHydrated => crate::RawIngestRouteFamily::HeliusTransaction,
Self::HttpBlockPolling => crate::RawIngestRouteFamily::HttpBlockPolling,
};
}
/// Returns the stable operator-facing route label without exposing physical endpoint metadata.
#[must_use]
pub(crate) const fn label(self) -> &'static str {
return match self {
Self::YellowstoneHydrated => "Yellowstone + HTTP hydration",
Self::StandardLogsHydrated => "Standard Logs + HTTP hydration",
Self::StandardBlockDirect => "Standard Block direct",
Self::HeliusTransactionHydrated => "Helius Transaction + HTTP hydration",
Self::HttpBlockPolling => "HTTP Block Polling",
};
}
}
/// Stable application-owned route families used only for safe presentation and orchestration.
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestRouteFamily.ts")]
pub(crate) enum RawIngestRouteFamily {
/// Yellowstone gRPC subscription route.
Yellowstone,
/// Standard Solana logs WebSocket route.
StandardLogs,
/// Standard Solana block WebSocket route.
StandardBlock,
/// Helius transaction WebSocket route.
HeliusTransaction,
/// HTTP block polling route.
HttpBlockPolling,
}
/// Safe reasons explaining why one logical route cannot currently be composed from Config.
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestRouteUnavailableReason.ts")]
pub(crate) enum RawIngestRouteUnavailableReason {
/// The selected composite or one of its typed Config components could not be resolved safely.
ProfileUnresolved,
/// Transport and Store do not prove one identical logical network.
NetworkMismatch,
/// No HTTP role can satisfy `getTransaction` for same-network hydration.
MissingHttpGetTransaction,
/// No HTTP role can satisfy the complete block-polling method set.
MissingHttpBlockScan,
/// No enabled standard WebSocket endpoint explicitly declares `Logs`.
MissingWsLogsCapability,
/// No enabled standard WebSocket endpoint explicitly declares `Block`.
MissingWsBlockCapability,
/// No enabled Helius WebSocket endpoint explicitly declares `HeliusTransaction`.
MissingHeliusTransactionCapability,
/// No enabled same-network Yellowstone gRPC endpoint is configured.
MissingYellowstoneGrpc,
/// A required Config secret is not resolved for the selected composite profile.
MissingRequiredSecret,
}
/// Stable Raw Transaction Ingest Desk route lifecycle states.
@@ -59,25 +125,104 @@ impl crate::RawIngestRouteState {
}
}
/// Static route/state foundation exposed by the scaffold before Config route inventory exists.
/// Static route/state foundation retained as the bounded vocabulary behind the live Config inventory.
#[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/RawIngestRouteFoundationDto.ts")]
pub(crate) struct RawIngestRouteFoundationDto {
/// Five stable V1 logical route identifiers; this is not a composability claim.
pub(crate) route_ids: std::vec::Vec<crate::RawIngestRouteId>,
/// Stable lifecycle state vocabulary reserved for later runtime projections.
/// Stable lifecycle state vocabulary reserved for runtime projections.
pub(crate) states: std::vec::Vec<crate::RawIngestRouteState>,
}
impl crate::RawIngestRouteFoundationDto {
/// Builds the static scaffold contract without resolving endpoints, secrets or Worker resources.
/// Builds the bounded route/state vocabulary without resolving endpoints, secrets or Worker resources.
#[must_use]
pub(crate) fn scaffold() -> Self {
return Self { route_ids: crate::RawIngestRouteId::all(), states: crate::RawIngestRouteState::all() };
}
}
/// Safe Config-only composability projection for one logical route.
#[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/RawIngestRouteDto.ts")]
pub(crate) struct RawIngestRouteDto {
/// Stable route family used for presentation and bounded orchestration.
pub(crate) family: crate::RawIngestRouteFamily,
/// Stable operator-facing label with no physical resource identity.
pub(crate) label: String,
/// Logical network proven by Config when available.
pub(crate) network: std::option::Option<String>,
/// Safe reason explaining an unavailable route.
pub(crate) reason: std::option::Option<crate::RawIngestRouteUnavailableReason>,
/// Stable logical V1 route identifier.
pub(crate) route_id: crate::RawIngestRouteId,
/// Whether all Config requirements for the route are currently composable.
pub(crate) selectable: bool,
/// Config-level lifecycle projection; `Configured` is not a runtime health claim.
pub(crate) state: crate::RawIngestRouteState,
}
impl crate::RawIngestRouteDto {
/// Builds one route that is composable from Config without claiming runtime operability.
#[must_use]
pub(crate) fn configured(route_id: crate::RawIngestRouteId, network: &str) -> Self {
return Self {
family: route_id.family(),
label: route_id.label().to_owned(),
network: std::option::Option::Some(network.to_owned()),
reason: std::option::Option::None,
route_id,
selectable: true,
state: crate::RawIngestRouteState::Configured,
};
}
/// Builds one route that is not composable from the current Config profile.
#[must_use]
pub(crate) fn unavailable(route_id: crate::RawIngestRouteId, network: std::option::Option<&str>, reason: crate::RawIngestRouteUnavailableReason) -> Self {
return Self {
family: route_id.family(),
label: route_id.label().to_owned(),
network: network.map(str::to_owned),
reason: std::option::Option::Some(reason),
route_id,
selectable: false,
state: crate::RawIngestRouteState::Unavailable,
};
}
}
/// Safe route inventory for one logical Raw Transaction Ingest Desk composite profile.
#[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/RawIngestProfileInventoryDto.ts")]
pub(crate) struct RawIngestProfileInventoryDto {
/// Whether this profile is the composite's autonomous default.
pub(crate) is_default: bool,
/// Logical network proven from the resolved Config profile when available.
pub(crate) network: std::option::Option<String>,
/// Stable composite profile identifier.
pub(crate) profile_id: String,
/// Five logical routes in stable V1 presentation order.
pub(crate) routes: std::vec::Vec<crate::RawIngestRouteDto>,
}
/// Deterministic Config-only route inventory returned to the frontend.
#[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/RawIngestRouteInventoryDto.ts")]
pub(crate) struct RawIngestRouteInventoryDto {
/// Composite default profile identifier.
pub(crate) default_profile: String,
/// Monotonic application-owned generation incremented after each successful inventory rebuild.
pub(crate) generation: u32,
/// Safe inventories for every validated composite profile in source order.
pub(crate) profiles: std::vec::Vec<crate::RawIngestProfileInventoryDto>,
}
#[cfg(test)]
#[path = "../unit_tests/dto_route.rs"]
mod tests;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/errors.rs
// version: 1
// version: 2
//! Stable Raw Transaction Ingest Desk application error codes.
@@ -20,6 +20,9 @@ pub(crate) const ERROR_CODE_FRONTEND_LOG_TARGET_INVALID: ksp_core_lib::ErrorCode
/// Raw Transaction Ingest Desk could not install managed Logging or its safe fallback.
pub(crate) const ERROR_CODE_LOGGING_BOOTSTRAP_FAILED: ksp_core_lib::ErrorCode =
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "logging_bootstrap_failed");
/// Config-only route inventory cannot be projected safely from the validated composite.
pub(crate) const ERROR_CODE_ROUTE_INVENTORY_INVALID: ksp_core_lib::ErrorCode =
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "route_inventory_invalid");
/// Splash readiness was invoked from a window other than the splash window.
pub(crate) const ERROR_CODE_SPLASH_ORIGIN_INVALID: ksp_core_lib::ErrorCode =
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "splash_origin_invalid");

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/lib.rs
// version: 1
// version: 2
//! Tauri desktop application scaffold for composing and supervising KSP live RAW transaction ingest routes.
@@ -15,6 +15,7 @@ mod dto_route;
mod errors;
mod frontend_logging;
mod logging_runtime;
mod route_inventory;
mod splash;
mod tauri;
mod tw_main;
@@ -33,6 +34,8 @@ pub(crate) use self::bootstrap::config_management;
pub(crate) use self::bootstrap::initialize_logging;
/// Loads the Raw Transaction Ingest Desk composite selected by its registered logical file identifier.
pub(crate) use self::bootstrap::load_raw_transaction_ingest_desk_composite;
/// Loads one explicit or default Raw Transaction Ingest Desk composite profile.
pub(crate) use self::bootstrap::load_raw_transaction_ingest_desk_composite_profile;
/// Resolves one required standard Config profile from the Raw Transaction Ingest Desk composite.
pub(crate) use self::bootstrap::required_composite_component_profile;
/// Composite-local identifier for the standard Logging component.
@@ -45,6 +48,8 @@ pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_TRANSPORT;
pub(crate) use self::constants::TRACING_DOMAIN_BOOTSTRAP;
/// Structured domain used by technical frontend events.
pub(crate) use self::constants::TRACING_DOMAIN_FRONTEND;
/// Structured domain used while rebuilding Config-only route inventory.
pub(crate) use self::constants::TRACING_DOMAIN_INVENTORY;
/// Structured domain used by the Raw Transaction Ingest Desk shell.
pub(crate) use self::constants::TRACING_DOMAIN_SHELL;
/// Structured domain used by Tauri window lifecycle operations.
@@ -61,12 +66,22 @@ pub(crate) use self::constants::TRACING_TARGET_FRONTEND_SPLASH;
pub(crate) use self::dto_common::CommandErrorDto;
/// Safe scaffold/runtime snapshot exposed to the shell.
pub(crate) use self::dto_common::ShellStatusDto;
/// Static route/state foundation exposed before route inventory is implemented.
/// Safe route inventory for one logical composite profile.
pub(crate) use self::dto_route::RawIngestProfileInventoryDto;
/// Safe Config-only composability projection for one logical route.
pub(crate) use self::dto_route::RawIngestRouteDto;
/// Stable application-owned route family.
pub(crate) use self::dto_route::RawIngestRouteFamily;
/// Static route/state foundation retained as the bounded vocabulary behind the Config inventory.
pub(crate) use self::dto_route::RawIngestRouteFoundationDto;
/// Stable logical V1 route identifiers owned by the Desk.
pub(crate) use self::dto_route::RawIngestRouteId;
/// Stable route lifecycle states reserved for later runtime projections.
/// Complete deterministic Config-only route inventory.
pub(crate) use self::dto_route::RawIngestRouteInventoryDto;
/// Stable route lifecycle states reserved for runtime projections.
pub(crate) use self::dto_route::RawIngestRouteState;
/// Safe reason explaining why one logical route is not composable from Config.
pub(crate) use self::dto_route::RawIngestRouteUnavailableReason;
/// Shared Raw Transaction Ingest Desk application state is internally inconsistent.
pub(crate) use self::errors::ERROR_CODE_APP_STATE_INVALID;
/// Shared Raw Transaction Ingest Desk runtime state cannot be locked safely.
@@ -79,6 +94,8 @@ pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID;
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_TARGET_INVALID;
/// Managed and fallback Logging initialization failed.
pub(crate) use self::errors::ERROR_CODE_LOGGING_BOOTSTRAP_FAILED;
/// Config-only route inventory cannot be projected safely.
pub(crate) use self::errors::ERROR_CODE_ROUTE_INVENTORY_INVALID;
/// Splash readiness originated from an invalid window.
pub(crate) use self::errors::ERROR_CODE_SPLASH_ORIGIN_INVALID;
/// Splash timing configuration is invalid.
@@ -95,6 +112,8 @@ pub(crate) use self::frontend_logging::FrontendLogPayloadDto;
pub(crate) use self::frontend_logging::emit_frontend_log_event;
/// Creates the stable runtime identity for this Raw Transaction Ingest Desk process launch.
pub(crate) use self::logging_runtime::launch_identity;
/// Rebuilds the complete safe route inventory from validated Config without runtime I/O.
pub(crate) use self::route_inventory::build_route_inventory;
/// Command emitted by Rust to the splash frontend.
pub(crate) use self::splash::SplashOrderDto;
/// Runtime timings used by the common desk splash lifecycle.

View File

@@ -0,0 +1,375 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/route_inventory.rs
// version: 1
//! Config-only logical route inventory for Raw Transaction Ingest Desk.
struct CompositeProfileCatalog {
default_profile: String,
profile_ids: std::vec::Vec<String>,
}
/// Rebuilds the complete safe route inventory from validated Config without opening any network, Store or Worker resource.
pub(crate) fn build_route_inventory(management: &ksp_config_lib::ConfigManagement, generation: u32) -> ksp_core_lib::Result<crate::RawIngestRouteInventoryDto> {
let catalog = composite_profile_catalog(management);
let catalog = match catalog {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let environment = ksp_config_lib::ConfigEnvironment::load();
let environment = match environment {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut profiles = std::vec::Vec::with_capacity(catalog.profile_ids.len());
for profile_id in &catalog.profile_ids {
profiles.push(build_profile_inventory(management, &environment, profile_id.as_str(), profile_id == &catalog.default_profile));
}
ksp_logging_lib::debug!(
target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_INVENTORY,
generation,
profile_count = profiles.len(),
"rebuilt Raw Transaction Ingest Desk Config-only route inventory"
);
return std::result::Result::Ok(crate::RawIngestRouteInventoryDto { default_profile: catalog.default_profile, generation, profiles });
}
fn composite_profile_catalog(management: &ksp_config_lib::ConfigManagement) -> ksp_core_lib::Result<CompositeProfileCatalog> {
let file_id = ksp_config_lib::ConfigFileId::new(ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_RAW_TRANSACTION_INGEST_DESK);
let file_id = match file_id {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let document = management.engine().load_validated_document(&file_id);
let document = match document {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let root = document.value().as_object();
let root = match root {
std::option::Option::Some(value) => value,
std::option::Option::None => return inventory_invalid("validated Raw Transaction Ingest Desk composite root is not an object"),
};
let default_profile = root.get("default_profile").and_then(|value| return value.as_str());
let default_profile = match default_profile {
std::option::Option::Some(value) if !value.is_empty() => value.to_owned(),
_ => return inventory_invalid("validated Raw Transaction Ingest Desk composite has no readable default profile"),
};
let profile_values = root.get("profiles").and_then(|value| return value.as_array());
let profile_values = match profile_values {
std::option::Option::Some(value) if !value.is_empty() => value,
_ => return inventory_invalid("validated Raw Transaction Ingest Desk composite has no profile inventory"),
};
let mut profile_ids = std::vec::Vec::with_capacity(profile_values.len());
let mut seen = std::collections::BTreeSet::<String>::new();
for profile in profile_values {
let profile_id = profile.get("profile_id").and_then(|value| return value.as_str());
let profile_id = match profile_id {
std::option::Option::Some(value) if !value.is_empty() => value,
_ => return inventory_invalid("validated Raw Transaction Ingest Desk composite contains an unreadable profile identifier"),
};
if !seen.insert(profile_id.to_owned()) {
return inventory_invalid("validated Raw Transaction Ingest Desk composite contains duplicate profile identifiers");
}
profile_ids.push(profile_id.to_owned());
}
if !seen.contains(default_profile.as_str()) {
return inventory_invalid("validated Raw Transaction Ingest Desk composite default profile is absent from the profile inventory");
}
return std::result::Result::Ok(CompositeProfileCatalog { default_profile, profile_ids });
}
fn build_profile_inventory(
management: &ksp_config_lib::ConfigManagement,
environment: &ksp_config_lib::ConfigEnvironment,
profile_id: &str,
is_default: bool,
) -> crate::RawIngestProfileInventoryDto {
let composite = crate::load_raw_transaction_ingest_desk_composite_profile(management, std::option::Option::Some(profile_id));
let composite = match composite {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return unavailable_profile(profile_id, is_default, std::option::Option::None, crate::RawIngestRouteUnavailableReason::ProfileUnresolved);
},
};
let transport_profile =
crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_TRANSPORT, ksp_config_lib::FILE_ID_STD_TRANSPORT);
let transport_profile = match transport_profile {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return unavailable_profile(profile_id, is_default, std::option::Option::None, crate::RawIngestRouteUnavailableReason::ProfileUnresolved);
},
};
let store_profile = crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_STORE, ksp_config_lib::FILE_ID_STD_STORE);
let store_profile = match store_profile {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return unavailable_profile(profile_id, is_default, std::option::Option::None, crate::RawIngestRouteUnavailableReason::ProfileUnresolved);
},
};
let transport = management.engine().resolve_transport_config_profile(&transport_profile, environment);
let store = management.engine().resolve_store_config_profile(&store_profile, environment);
let network_hint = resolved_network_hint(&transport, &store);
if transport.is_err() || store.is_err() {
let reason = resolution_failure_reason(&transport, &store);
return unavailable_profile(profile_id, is_default, network_hint.as_deref(), reason);
}
let transport = match transport {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return unavailable_profile(profile_id, is_default, network_hint.as_deref(), crate::RawIngestRouteUnavailableReason::ProfileUnresolved);
},
};
let store = match store {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return unavailable_profile(profile_id, is_default, network_hint.as_deref(), crate::RawIngestRouteUnavailableReason::ProfileUnresolved);
},
};
let store_network = store.settings().network().as_str().to_owned();
if !transport_matches_network(&transport, store_network.as_str()) {
return unavailable_profile(
profile_id,
is_default,
std::option::Option::Some(store_network.as_str()),
crate::RawIngestRouteUnavailableReason::NetworkMismatch,
);
}
let routes = build_configured_profile_routes(&transport, store_network.as_str());
return crate::RawIngestProfileInventoryDto {
is_default,
network: std::option::Option::Some(store_network),
profile_id: profile_id.to_owned(),
routes,
};
}
fn build_configured_profile_routes(transport: &ksp_config_lib::ResolvedTransportConfig, network: &str) -> std::vec::Vec<crate::RawIngestRouteDto> {
let has_get_transaction = http_role_supports_methods(transport.http_settings(), &["getTransaction"]);
let has_http_block_scan = http_role_supports_methods(transport.http_settings(), &["getSlot", "getBlocksWithLimit", "getBlock"]);
let has_yellowstone = has_yellowstone_grpc(transport, network);
let has_standard_logs =
has_ws_capability(transport, network, ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard, ksp_onchain_transport_lib::WsSubscriptionKind::Logs);
let has_standard_block =
has_ws_capability(transport, network, ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard, ksp_onchain_transport_lib::WsSubscriptionKind::Block);
let has_helius_transaction = has_ws_capability(
transport,
network,
ksp_onchain_transport_lib::WsProtocolKind::HeliusLaserStream,
ksp_onchain_transport_lib::WsSubscriptionKind::HeliusTransaction,
);
return vec![
route_with_requirements(
crate::RawIngestRouteId::YellowstoneHydrated,
network,
&[
(has_yellowstone, crate::RawIngestRouteUnavailableReason::MissingYellowstoneGrpc),
(has_get_transaction, crate::RawIngestRouteUnavailableReason::MissingHttpGetTransaction),
],
),
route_with_requirements(
crate::RawIngestRouteId::StandardLogsHydrated,
network,
&[
(has_standard_logs, crate::RawIngestRouteUnavailableReason::MissingWsLogsCapability),
(has_get_transaction, crate::RawIngestRouteUnavailableReason::MissingHttpGetTransaction),
],
),
route_with_requirements(
crate::RawIngestRouteId::StandardBlockDirect,
network,
&[(has_standard_block, crate::RawIngestRouteUnavailableReason::MissingWsBlockCapability)],
),
route_with_requirements(
crate::RawIngestRouteId::HeliusTransactionHydrated,
network,
&[
(has_helius_transaction, crate::RawIngestRouteUnavailableReason::MissingHeliusTransactionCapability),
(has_get_transaction, crate::RawIngestRouteUnavailableReason::MissingHttpGetTransaction),
],
),
route_with_requirements(
crate::RawIngestRouteId::HttpBlockPolling,
network,
&[(has_http_block_scan, crate::RawIngestRouteUnavailableReason::MissingHttpBlockScan)],
),
];
}
fn route_with_requirements(
route_id: crate::RawIngestRouteId,
network: &str,
requirements: &[(bool, crate::RawIngestRouteUnavailableReason)],
) -> crate::RawIngestRouteDto {
for (available, reason) in requirements.iter().copied() {
if !available {
return crate::RawIngestRouteDto::unavailable(route_id, std::option::Option::Some(network), reason);
}
}
return crate::RawIngestRouteDto::configured(route_id, network);
}
fn unavailable_profile(
profile_id: &str,
is_default: bool,
network: std::option::Option<&str>,
reason: crate::RawIngestRouteUnavailableReason,
) -> crate::RawIngestProfileInventoryDto {
let routes = crate::RawIngestRouteId::all()
.into_iter()
.map(|route_id| return crate::RawIngestRouteDto::unavailable(route_id, network, reason))
.collect::<std::vec::Vec<_>>();
return crate::RawIngestProfileInventoryDto { is_default, network: network.map(str::to_owned), profile_id: profile_id.to_owned(), routes };
}
fn resolution_failure_reason(
transport: &ksp_core_lib::Result<ksp_config_lib::ResolvedTransportConfig>,
store: &ksp_core_lib::Result<ksp_config_lib::ResolvedStoreConfig>,
) -> crate::RawIngestRouteUnavailableReason {
let transport_missing_secret = match transport {
std::result::Result::Err(error) => error.code() == ksp_config_lib::ERROR_CODE_ENVIRONMENT_VARIABLE_MISSING,
std::result::Result::Ok(_) => false,
};
let store_missing_secret = match store {
std::result::Result::Err(error) => error.code() == ksp_config_lib::ERROR_CODE_ENVIRONMENT_VARIABLE_MISSING,
std::result::Result::Ok(_) => false,
};
if transport_missing_secret || store_missing_secret {
return crate::RawIngestRouteUnavailableReason::MissingRequiredSecret;
}
return crate::RawIngestRouteUnavailableReason::ProfileUnresolved;
}
fn resolved_network_hint(
transport: &ksp_core_lib::Result<ksp_config_lib::ResolvedTransportConfig>,
store: &ksp_core_lib::Result<ksp_config_lib::ResolvedStoreConfig>,
) -> std::option::Option<String> {
if let std::result::Result::Ok(store) = store {
return std::option::Option::Some(store.settings().network().as_str().to_owned());
}
if let std::result::Result::Ok(transport) = transport {
let networks = enabled_transport_networks(transport);
if networks.len() == 1 {
return networks.into_iter().next();
}
}
return std::option::Option::None;
}
fn transport_matches_network(transport: &ksp_config_lib::ResolvedTransportConfig, network: &str) -> bool {
let networks = enabled_transport_networks(transport);
return networks.len() == 1 && networks.contains(network);
}
fn enabled_transport_networks(transport: &ksp_config_lib::ResolvedTransportConfig) -> std::collections::BTreeSet<String> {
let mut networks = std::collections::BTreeSet::<String>::new();
for endpoint in transport.http_settings().endpoints() {
if endpoint.enabled() {
networks.insert(endpoint.cluster().as_str().to_owned());
}
}
if let std::option::Option::Some(settings) = transport.ws_settings() {
for endpoint in settings.endpoints() {
if endpoint.enabled() {
networks.insert(endpoint.cluster().as_str().to_owned());
}
}
}
if let std::option::Option::Some(settings) = transport.grpc_settings() {
for endpoint in settings.endpoints() {
if endpoint.enabled() {
networks.insert(endpoint.cluster().as_str().to_owned());
}
}
}
return networks;
}
fn has_ws_capability(
transport: &ksp_config_lib::ResolvedTransportConfig,
network: &str,
protocol: ksp_onchain_transport_lib::WsProtocolKind,
capability: ksp_onchain_transport_lib::WsSubscriptionKind,
) -> bool {
let settings = match transport.ws_settings() {
std::option::Option::Some(value) => value,
std::option::Option::None => return false,
};
for endpoint in settings.endpoints() {
if endpoint.enabled() && endpoint.cluster().as_str() == network && endpoint.protocol() == protocol && endpoint.supports_subscription(capability) {
return true;
}
}
return false;
}
fn has_yellowstone_grpc(transport: &ksp_config_lib::ResolvedTransportConfig, network: &str) -> bool {
let settings = match transport.grpc_settings() {
std::option::Option::Some(value) => value,
std::option::Option::None => return false,
};
return settings.endpoints().iter().any(|endpoint| return endpoint.enabled() && endpoint.cluster().as_str() == network);
}
fn http_role_supports_methods(settings: &ksp_onchain_transport_lib::HttpTransportSettings, methods: &[&'static str]) -> bool {
let mut request_kinds = std::vec::Vec::<&'static str>::with_capacity(methods.len());
for method_name in methods {
let descriptor = ksp_onchain_transport_lib::find_http_rpc_method(method_name);
let descriptor = match descriptor {
std::option::Option::Some(value) => value,
std::option::Option::None => return false,
};
request_kinds.push(descriptor.request_kind());
}
let mut role_names = std::collections::BTreeSet::<String>::new();
for endpoint in settings.endpoints() {
if !endpoint.enabled() {
continue;
}
for role in endpoint.roles() {
if role.enabled() {
role_names.insert(role.role().as_str().to_owned());
}
}
}
for role_name in role_names {
let mut complete = true;
for request_kind in &request_kinds {
if !http_role_supports_request_kind(settings, role_name.as_str(), request_kind) {
complete = false;
break;
}
}
if complete {
return true;
}
}
return false;
}
fn http_role_supports_request_kind(settings: &ksp_onchain_transport_lib::HttpTransportSettings, role_name: &str, request_kind: &str) -> bool {
for endpoint in settings.endpoints() {
if !endpoint.enabled() {
continue;
}
for role in endpoint.roles() {
if !role.enabled() || role.role().as_str() != role_name {
continue;
}
for capability in role.request_kinds() {
if capability.is_wildcard() || capability.as_str() == request_kind {
return true;
}
}
}
}
return false;
}
fn inventory_invalid(message: &'static str) -> ksp_core_lib::Result<CompositeProfileCatalog> {
return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_ROUTE_INVENTORY_INVALID, message));
}
#[cfg(test)]
#[path = "../unit_tests/route_inventory.rs"]
mod tests;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/tauri.rs
// version: 2
// version: 3
//! Tauri runtime assembly for the KSP Raw Transaction Ingest desktop application.
@@ -74,7 +74,13 @@ fn configure_packaged_runtime(context: &tauri::Context<tauri::Wry>) -> ksp_core_
#[allow(clippy::question_mark_used)] // Tauri generates the question-mark operator internally for async command dispatch.
fn configure_commands(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
return builder.invoke_handler(tauri::generate_handler![emit_frontend_log, get_route_foundation, get_runtime_status, splash_frontend_ready]);
return builder.invoke_handler(tauri::generate_handler![
emit_frontend_log,
get_route_foundation,
get_route_inventory,
get_runtime_status,
splash_frontend_ready
]);
}
fn project_command_error(command: &'static str, domain: &'static str, error: &ksp_core_lib::Error) -> crate::CommandErrorDto {
@@ -103,6 +109,15 @@ fn get_route_foundation(state: tauri::State<'_, crate::AppState>) -> crate::RawI
return state.route_foundation();
}
#[tauri::command]
fn get_route_inventory(state: tauri::State<'_, crate::AppState>) -> std::result::Result<crate::RawIngestRouteInventoryDto, crate::CommandErrorDto> {
let result = state.route_inventory();
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("get_route_inventory", crate::TRACING_DOMAIN_INVENTORY, &error)),
};
}
#[tauri::command]
fn get_runtime_status(state: tauri::State<'_, crate::AppState>) -> std::result::Result<crate::ShellStatusDto, crate::CommandErrorDto> {
let result = state.shell_status();

View File

@@ -1,7 +1,7 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/tests/dependency_boundary.rs
// version: 1
// version: 2
//! Dependency-boundary canaries for the Raw Transaction Ingest Desk scaffold.
//! Dependency-boundary canaries for Raw Transaction Ingest Desk Config-only route inventory.
#![forbid(unsafe_code)]
#![deny(unreachable_pub)]
@@ -21,13 +21,12 @@ fn read_text(path: &std::path::Path) -> String {
}
#[test]
fn pre_005_manifest_is_scaffold_only_and_backend_neutral() {
fn pre_006_manifest_opens_only_transport_for_typed_config_composability() {
let manifest = read_text(app_root().join("Cargo.toml").as_path());
for required in ["ksp-config-lib", "ksp-core-lib", "ksp-logging-lib", "tauri-plugin-tracing"] {
assert!(manifest.contains(required), "missing scaffold dependency {required}");
for required in ["ksp-config-lib", "ksp-core-lib", "ksp-logging-lib", "ksp-onchain-transport-lib", "tauri-plugin-tracing"] {
assert!(manifest.contains(required), "missing pre.006 dependency {required}");
}
for forbidden in [
"ksp-onchain-transport-lib",
"ksp-store-lib",
"ksp-store-api",
"ksp-store-postgres-lib",
@@ -39,12 +38,12 @@ fn pre_005_manifest_is_scaffold_only_and_backend_neutral() {
"tonic",
"yellowstone-grpc",
] {
assert!(!manifest.contains(forbidden), "scaffold opens forbidden dependency {forbidden}");
assert!(!manifest.contains(forbidden), "pre.006 opens forbidden dependency {forbidden}");
}
}
#[test]
fn pre_005_production_sources_do_not_construct_transport_store_or_worker_runtime() {
fn pre_006_production_sources_use_transport_settings_only_without_runtime_io_or_store_open() {
let root = app_root().join("src");
let entries = std::fs::read_dir(root.as_path());
assert!(entries.is_ok());
@@ -57,11 +56,13 @@ fn pre_005_production_sources_do_not_construct_transport_store_or_worker_runtime
let source = read_text(path.as_path());
for forbidden in [
"HttpTransportPool",
"WsTransportSettings",
"WsProtocolSession",
"YellowstoneGrpcChannel",
"ksp_store_lib::Store",
"Store::open",
"RawTransactionIngestWorker",
"RawTransactionIngestRuntimeResources",
"start_with_runtime_resources",
"request_stop",
] {
assert!(!source.contains(forbidden), "{} contains premature runtime marker {forbidden}", path.display());
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/tests/desktop_contract.rs
// version: 2
// version: 3
//! Desktop scaffold contract canaries for Raw Transaction Ingest Desk.
@@ -108,7 +108,7 @@ fn pre_005_splash_assets_match_existing_ksp_desk_template_byte_exact() {
}
#[test]
fn pre_005_route_foundation_is_backend_owned_and_has_exact_five_ids_and_seven_states() {
fn pre_006_route_inventory_is_backend_owned_and_keeps_exact_five_ids_and_seven_states() {
let dto = read_text(app_root().join("src/dto_route.rs").as_path());
for route in ["YellowstoneHydrated", "StandardLogsHydrated", "StandardBlockDirect", "HeliusTransactionHydrated", "HttpBlockPolling"] {
assert!(dto.contains(route));
@@ -118,7 +118,8 @@ fn pre_005_route_foundation_is_backend_owned_and_has_exact_five_ids_and_seven_st
}
let tauri = read_text(app_root().join("src/tauri.rs").as_path());
assert!(tauri.contains("get_route_foundation"));
for forbidden in ["start_worker", "stop_worker", "RawTransactionIngestWorker", "Store::open", "HttpTransportPool"] {
assert!(tauri.contains("get_route_inventory"));
for forbidden in ["start_worker", "stop_worker", "RawTransactionIngestWorker", "Store::open"] {
assert!(!tauri.contains(forbidden), "scaffold Tauri surface contains premature runtime marker {forbidden}");
}
}
@@ -153,3 +154,18 @@ fn pre_005_fix_002_shell_reuses_common_light_template_and_bounds_route_foundatio
assert!(styles.contains(required), "missing bounded layout marker: {required}");
}
}
#[test]
fn pre_006_frontend_projects_profile_network_generation_and_safe_route_reasons() {
let html = read_text(app_root().join("frontend/main.html").as_path());
for required in ["id=\"routeProfile\"", "id=\"routeNetwork\"", "id=\"routeInventoryGeneration\"", "id=\"routeFoundation\""] {
assert!(html.contains(required), "missing route inventory UI marker: {required}");
}
let main = read_text(app_root().join("frontend/ts/main.ts").as_path());
for required in ["get_route_inventory", "missing_ws_block_capability", "missing_required_secret", "Config route inventory"] {
assert!(main.contains(required), "missing route inventory frontend marker: {required}");
}
for forbidden in ["endpointUrl", "connectionUri", "apiKey", "sourceKey", "x-token"] {
assert!(!main.contains(forbidden), "frontend exposes forbidden physical marker: {forbidden}");
}
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/tests/desktop_security.rs
// version: 1
// version: 2
//! Desktop security canaries for the Raw Transaction Ingest Desk scaffold.
@@ -58,15 +58,27 @@ fn pre_005_frontend_has_no_network_persistence_or_native_dialog_surface() {
}
#[test]
fn pre_005_frontend_tracing_is_control_and_command_oriented_only() {
fn pre_006_frontend_tracing_is_control_inventory_and_command_oriented_only() {
let main = read_text(app_root().join("frontend/ts/main.ts").as_path());
assert!(main.contains("frontend control clicked"));
assert!(main.contains("navigation tab clicked"));
assert!(main.contains("runtime status refresh requested"));
assert!(main.contains("route foundation refresh requested"));
assert!(main.contains("Config route inventory refresh requested"));
assert!(main.contains("logical profile selected"));
let invoke = read_text(app_root().join("frontend/ts/invoke.ts").as_path());
assert!(invoke.contains("Frontend IPC command requested"));
for forbidden in ["apiKey", "authorization", "connectionUri", "endpointUrl", "sourceKey", "rawTransaction", "transactionPayload"] {
for forbidden in ["apiKey", "authorization", "connectionUri", "endpointUrl", "sourceKey", "rawTransaction", "transactionPayload", "x-token"] {
assert!(!main.contains(forbidden), "frontend contains forbidden sensitive marker {forbidden}");
}
}
#[test]
fn pre_006_route_inventory_dto_surface_excludes_endpoint_secret_and_physical_store_fields() {
let dto = read_text(app_root().join("src/dto_route.rs").as_path());
for required in ["RawIngestRouteInventoryDto", "RawIngestProfileInventoryDto", "RawIngestRouteUnavailableReason"] {
assert!(dto.contains(required));
}
for forbidden in ["endpoint_name", "endpoint_url", "api_key", "connection_uri", "source_key", "secret_metadata", "worker_handle"] {
assert!(!dto.contains(forbidden), "route DTO contains forbidden field marker {forbidden}");
}
}

View File

@@ -1,7 +1,7 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/tests/release_completeness.rs
// version: 2
// version: 3
//! Release-completeness canaries for the Raw Transaction Ingest Desk scaffold tranche.
//! Release-completeness canaries for Raw Transaction Ingest Desk Config-only route inventory.
#![forbid(unsafe_code)]
#![deny(unreachable_pub)]
@@ -21,7 +21,7 @@ fn read_text(path: &std::path::Path) -> String {
}
#[test]
fn pre_005_production_module_inventory_is_exact_and_scaffold_only() {
fn pre_006_production_module_inventory_adds_only_route_inventory_to_the_scaffold() {
let lib = read_text(app_root().join("src/lib.rs").as_path());
let expected = [
"mod app_state;",
@@ -32,6 +32,7 @@ fn pre_005_production_module_inventory_is_exact_and_scaffold_only() {
"mod errors;",
"mod frontend_logging;",
"mod logging_runtime;",
"mod route_inventory;",
"mod splash;",
"mod tauri;",
"mod tw_main;",
@@ -44,14 +45,14 @@ fn pre_005_production_module_inventory_is_exact_and_scaffold_only() {
}
#[test]
fn pre_005_public_surface_exposes_only_application_run_entry_point() {
fn pre_006_public_surface_still_exposes_only_application_run_entry_point() {
let lib = read_text(app_root().join("src/lib.rs").as_path());
let public_reexports = lib.lines().filter(|line| return line.starts_with("pub use ")).collect::<std::vec::Vec<_>>();
assert_eq!(public_reexports, vec!["pub use self::tauri::run;"]);
}
#[test]
fn pre_005_scaffold_keeps_start_stop_inventory_and_runtime_for_later_tranches() {
fn pre_006_config_inventory_keeps_start_stop_and_runtime_resources_for_later_tranches() {
let app = read_text(app_root().join("src/app_state.rs").as_path());
let tauri = read_text(app_root().join("src/tauri.rs").as_path());
for forbidden in [
@@ -68,3 +69,22 @@ fn pre_005_scaffold_keeps_start_stop_inventory_and_runtime_for_later_tranches()
assert!(!tauri.contains(forbidden));
}
}
#[test]
fn pre_006_inventory_source_is_config_only_and_contains_all_five_capability_requirements() {
let inventory = read_text(app_root().join("src/route_inventory.rs").as_path());
for required in [
"WsSubscriptionKind::Logs",
"WsSubscriptionKind::Block",
"WsSubscriptionKind::HeliusTransaction",
"getTransaction",
"getBlocksWithLimit",
"MissingYellowstoneGrpc",
"MissingRequiredSecret",
] {
assert!(inventory.contains(required), "missing pre.006 inventory requirement {required}");
}
for forbidden in ["connect(", "Store::open", "start_with_runtime_resources", "request_stop"] {
assert!(!inventory.contains(forbidden), "pre.006 inventory performs premature runtime operation {forbidden}");
}
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/unit_tests/dto_route.rs
// version: 1
// version: 2
#[test]
fn pre_005_route_ids_are_exact_and_stably_ordered() {
@@ -25,3 +25,36 @@ fn pre_005_route_states_are_exact_without_runtime_claims() {
assert_eq!(value, serde_json::json!(["unavailable", "configured", "starting", "running", "stopping", "stopped", "faulted"]));
}
}
#[test]
fn pre_006_unavailable_reason_codes_are_exact_and_safe() {
let values = [
crate::RawIngestRouteUnavailableReason::ProfileUnresolved,
crate::RawIngestRouteUnavailableReason::NetworkMismatch,
crate::RawIngestRouteUnavailableReason::MissingHttpGetTransaction,
crate::RawIngestRouteUnavailableReason::MissingHttpBlockScan,
crate::RawIngestRouteUnavailableReason::MissingWsLogsCapability,
crate::RawIngestRouteUnavailableReason::MissingWsBlockCapability,
crate::RawIngestRouteUnavailableReason::MissingHeliusTransactionCapability,
crate::RawIngestRouteUnavailableReason::MissingYellowstoneGrpc,
crate::RawIngestRouteUnavailableReason::MissingRequiredSecret,
];
let serialized = serde_json::to_value(values);
assert!(serialized.is_ok());
if let std::result::Result::Ok(serialized) = serialized {
assert_eq!(
serialized,
serde_json::json!([
"profile_unresolved",
"network_mismatch",
"missing_http_get_transaction",
"missing_http_block_scan",
"missing_ws_logs_capability",
"missing_ws_block_capability",
"missing_helius_transaction_capability",
"missing_yellowstone_grpc",
"missing_required_secret"
])
);
}
}

View File

@@ -0,0 +1,112 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/unit_tests/route_inventory.rs
// version: 1
#[test]
fn pre_006_route_requirement_projection_is_fail_closed_and_ordered() {
let route = super::route_with_requirements(
crate::RawIngestRouteId::StandardLogsHydrated,
"devnet",
&[
(false, crate::RawIngestRouteUnavailableReason::MissingWsLogsCapability),
(false, crate::RawIngestRouteUnavailableReason::MissingHttpGetTransaction),
],
);
assert!(!route.selectable);
assert_eq!(route.state, crate::RawIngestRouteState::Unavailable);
assert_eq!(route.reason, std::option::Option::Some(crate::RawIngestRouteUnavailableReason::MissingWsLogsCapability));
assert_eq!(route.network.as_deref(), std::option::Option::Some("devnet"));
let configured = super::route_with_requirements(
crate::RawIngestRouteId::HttpBlockPolling,
"mainnet",
&[(true, crate::RawIngestRouteUnavailableReason::MissingHttpBlockScan)],
);
assert!(configured.selectable);
assert_eq!(configured.state, crate::RawIngestRouteState::Configured);
assert_eq!(configured.reason, std::option::Option::None);
}
#[test]
fn pre_006_unavailable_profile_projects_all_five_routes_without_physical_material() {
let profile = super::unavailable_profile(
"provider_profile",
false,
std::option::Option::Some("testnet"),
crate::RawIngestRouteUnavailableReason::MissingRequiredSecret,
);
assert_eq!(profile.routes.len(), 5);
assert_eq!(profile.network.as_deref(), std::option::Option::Some("testnet"));
for route in profile.routes {
assert!(!route.selectable);
assert_eq!(route.reason, std::option::Option::Some(crate::RawIngestRouteUnavailableReason::MissingRequiredSecret));
}
}
#[test]
fn pre_006_committed_devnet_public_inventory_proves_only_config_composability() {
let arguments = std::vec::Vec::<std::ffi::OsString>::new();
let management = crate::config_management(arguments.as_slice());
assert!(management.is_ok());
let management = match management {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let inventory = crate::build_route_inventory(&management, 7);
assert!(inventory.is_ok());
let inventory = match inventory {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(inventory.generation, 7);
assert_eq!(inventory.default_profile, "devnet_public");
let profile = inventory.profiles.iter().find(|profile| return profile.profile_id == "devnet_public");
assert!(profile.is_some());
let profile = match profile {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
assert!(profile.is_default);
assert_eq!(profile.network.as_deref(), std::option::Option::Some("devnet"));
assert_eq!(profile.routes.len(), 5);
assert_route(
profile,
crate::RawIngestRouteId::YellowstoneHydrated,
false,
std::option::Option::Some(crate::RawIngestRouteUnavailableReason::MissingYellowstoneGrpc),
);
assert_route(profile, crate::RawIngestRouteId::StandardLogsHydrated, true, std::option::Option::None);
assert_route(
profile,
crate::RawIngestRouteId::StandardBlockDirect,
false,
std::option::Option::Some(crate::RawIngestRouteUnavailableReason::MissingWsBlockCapability),
);
assert_route(
profile,
crate::RawIngestRouteId::HeliusTransactionHydrated,
false,
std::option::Option::Some(crate::RawIngestRouteUnavailableReason::MissingHeliusTransactionCapability),
);
assert_route(profile, crate::RawIngestRouteId::HttpBlockPolling, true, std::option::Option::None);
let serialized = serde_json::to_string(&inventory);
assert!(serialized.is_ok());
if let std::result::Result::Ok(serialized) = serialized {
for forbidden in ["http://", "https://", "ws://", "wss://", "api-key", "x-token", "connection_uri", "source_key", "endpoint_name"] {
assert!(!serialized.contains(forbidden), "safe inventory leaked forbidden marker {forbidden}");
}
}
}
fn assert_route(
profile: &crate::RawIngestProfileInventoryDto,
route_id: crate::RawIngestRouteId,
selectable: bool,
reason: std::option::Option<crate::RawIngestRouteUnavailableReason>,
) {
let route = profile.routes.iter().find(|route| return route.route_id == route_id);
assert!(route.is_some());
if let std::option::Option::Some(route) = route {
assert_eq!(route.selectable, selectable);
assert_eq!(route.reason, reason);
assert_eq!(route.state, if selectable { crate::RawIngestRouteState::Configured } else { crate::RawIngestRouteState::Unavailable });
}
}