v0.3.7-pre.006

This commit is contained in:
2026-09-02 15:05:18 +02:00
parent 57ff11b774
commit 3c4ae51ae2
18 changed files with 475 additions and 82 deletions

View File

@@ -1,12 +1,12 @@
# file: Cargo.toml # file: Cargo.toml
# version: 422 # version: 423
[workspace] [workspace]
resolver = "3" resolver = "3"
members = ["crates/ksp-app-backfill-desk", "crates/ksp-app-config-desk", "crates/ksp-app-solprices-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-store-api", "crates/ksp-store-lib", "crates/ksp-store-postgres-lib", "crates/ksp-wallet-lib"] members = ["crates/ksp-app-backfill-desk", "crates/ksp-app-config-desk", "crates/ksp-app-solprices-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-store-api", "crates/ksp-store-lib", "crates/ksp-store-postgres-lib", "crates/ksp-wallet-lib"]
[workspace.package] [workspace.package]
version = "0.3.7-pre.5.fix.1" version = "0.3.7-pre.6"
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"

View File

@@ -1,6 +1,6 @@
{ {
"format_version": 1, "format_version": 1,
"default_profile": "devnet", "default_profile": "mainnet",
"profiles": [ "profiles": [
{ {
"profile_id": "devnet", "profile_id": "devnet",
@@ -28,12 +28,12 @@
{ {
"component_id": "logging", "component_id": "logging",
"file_id": "cfg.std.logging", "file_id": "cfg.std.logging",
"profile_id": "console_info" "profile_id": "supertrace"
}, },
{ {
"component_id": "transport", "component_id": "transport",
"file_id": "cfg.std.transport", "file_id": "cfg.std.transport",
"profile_id": "mainnet_public" "profile_id": "mainnet_backfill_pool"
}, },
{ {
"component_id": "store", "component_id": "store",

View File

@@ -178,6 +178,101 @@
} }
] ]
}, },
{
"profile_id": "mainnet_backfill_pool",
"endpoints": [
{
"name": "solana_mainnet_public",
"enabled": true,
"provider": "solana-public",
"cluster": "mainnet-beta",
"url": "${KSP_PUBLIC_SOLANA_MAINNET_HTTP_URL:-https://api.mainnet-beta.solana.com}",
"connect_timeout_ms": 5000,
"request_timeout_ms": 15000,
"max_idle_connections_per_host": 8,
"roles": [
{
"role": "backfill_pool",
"enabled": true,
"request_kinds": [
"*"
],
"priority": 100,
"limits": {
"requests_per_second": 5,
"burst_capacity": 10,
"max_concurrent_requests": 8,
"pause_after_rate_limit_ms": 1000
}
},
{
"role": "backfill_solana_public",
"enabled": true,
"request_kinds": [
"*"
],
"priority": 100,
"limits": {
"requests_per_second": 5,
"burst_capacity": 10,
"max_concurrent_requests": 8,
"pause_after_rate_limit_ms": 1000
}
}
]
},
{
"name": "publicnode_solana_mainnet_http",
"enabled": true,
"provider": "publicnode",
"cluster": "mainnet-beta",
"url": "https://solana-rpc.publicnode.com",
"connect_timeout_ms": 5000,
"request_timeout_ms": 15000,
"max_idle_connections_per_host": 8,
"roles": [
{
"role": "backfill_pool",
"enabled": true,
"request_kinds": [
"*"
],
"priority": 100,
"limits": {
"requests_per_second": 5,
"burst_capacity": 10,
"max_concurrent_requests": 8,
"pause_after_rate_limit_ms": 1000
}
},
{
"role": "backfill_publicnode",
"enabled": true,
"request_kinds": [
"*"
],
"priority": 100,
"limits": {
"requests_per_second": 5,
"burst_capacity": 10,
"max_concurrent_requests": 8,
"pause_after_rate_limit_ms": 1000
}
}
]
}
],
"ws_endpoints": [
{
"name": "solana_mainnet_public_ws",
"enabled": true,
"provider": "solana-public",
"cluster": "mainnet-beta",
"kind": "solana_standard",
"url": "${KSP_PUBLIC_SOLANA_MAINNET_WS_URL:-wss://api.mainnet-beta.solana.com}"
}
]
},
{ {
"profile_id": "publicnode_mainnet", "profile_id": "publicnode_mainnet",
"endpoints": [ "endpoints": [

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-app-backfill-desk/frontend/main.html --> <!-- file: crates/ksp-app-backfill-desk/frontend/main.html -->
<!-- version: 1 --> <!-- version: 2 -->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="fr"> <html lang="fr">
@@ -51,11 +51,34 @@
</div> </div>
<span id="appVersionBadge" class="badge text-bg-secondary"></span> <span id="appVersionBadge" class="badge text-bg-secondary"></span>
</div> </div>
<div class="app-placeholder"> <div class="row g-4">
<div class="col-xl-7">
<div class="card shadow-sm h-100">
<div class="card-header fw-semibold">Transport HTTP</div>
<div class="card-body">
<label for="backfillHttpRoute" class="form-label">Route de lecture</label>
<select id="backfillHttpRoute" class="form-select" disabled>
<option value="">Chargement des routes…</option>
</select>
<div id="backfillHttpRouteHelp" class="form-text">Le choix reste logique : aucune URL ni clé provider n'est exposée au frontend.</div>
<dl class="row mt-4 mb-0 app-runtime-list">
<dt class="col-sm-5">Réseau</dt><dd id="backfillNetwork" class="col-sm-7"></dd>
<dt class="col-sm-5">Transport</dt><dd id="backfillTransportReady" class="col-sm-7"></dd>
<dt class="col-sm-5">Store</dt><dd id="backfillStoreReady" class="col-sm-7"></dd>
<dt class="col-sm-5">Composition</dt><dd id="backfillCompositionReady" class="col-sm-7"></dd>
</dl>
<div id="backfillOptionsDiagnostic" class="alert alert-warning mt-3 mb-0" role="status" aria-live="polite" hidden></div>
</div>
</div>
</div>
<div class="col-xl-5">
<div class="app-placeholder h-100">
<i class="fa-solid fa-clock-rotate-left text-body-secondary" aria-hidden="true"></i> <i class="fa-solid fa-clock-rotate-left text-body-secondary" aria-hidden="true"></i>
<div> <div>
<h2 class="h5">Backfill runtime non ouvert dans pre.002</h2> <h2 class="h5">Campagne Backfill</h2>
<p class="text-body-secondary mb-0">Aucun Transport, Store ou Job Backfill n'est encore créé par le frontend.</p> <p class="text-body-secondary mb-0">Les scopes, commitments et le bouton Start seront branchés dans la tranche suivante. Le transport reste HTTP uniquement en v0.3.7.</p>
</div>
</div>
</div> </div>
</div> </div>
</section> </section>

View File

@@ -1,10 +1,12 @@
// file: crates/ksp-app-backfill-desk/frontend/ts/main.ts // file: crates/ksp-app-backfill-desk/frontend/ts/main.ts
// version: 1 // version: 2
import "bootstrap"; import "bootstrap";
import ResizeObserver from "resize-observer-polyfill"; import ResizeObserver from "resize-observer-polyfill";
import "simplebar"; import "simplebar";
import { getCurrentWindow } from "@tauri-apps/api/window"; import { getCurrentWindow } from "@tauri-apps/api/window";
import type { BackfillDeskOptionsDto } from "./bindings/ksp_app_backfill_desk/dto_common/BackfillDeskOptionsDto.ts";
import type { BackfillHttpRouteOptionDto } from "./bindings/ksp_app_backfill_desk/dto_common/BackfillHttpRouteOptionDto.ts";
import type { ShellStatusDto } from "./bindings/ksp_app_backfill_desk/dto_common/ShellStatusDto.ts"; import type { ShellStatusDto } from "./bindings/ksp_app_backfill_desk/dto_common/ShellStatusDto.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";
@@ -77,6 +79,74 @@ function bindNavigation(): void {
frontendTrace("main", "Backfill Desk navigation handlers installed"); frontendTrace("main", "Backfill Desk navigation handlers installed");
} }
function routeLabel(route: BackfillHttpRouteOptionDto): string {
if (route.pooled) {
return `Pool multi-provider — ${route.providers.join(" + ")}`;
}
return route.providers.length === 1 ? `Provider — ${route.providers[0]}` : route.role;
}
function renderBackfillOptions(options: BackfillDeskOptionsDto): void {
const select = document.querySelector<HTMLSelectElement>("#backfillHttpRoute");
if (select) {
const previous = select.value;
select.replaceChildren();
for (const route of options.httpRoutes) {
const option = document.createElement("option");
option.value = route.role;
option.textContent = routeLabel(route);
select.append(option);
}
select.disabled = !options.transportReady || options.httpRoutes.length === 0;
const reusable = options.httpRoutes.some(route => route.role === previous);
if (reusable) {
select.value = previous;
}
}
const values: Record<string, string> = {
backfillNetwork: options.configuredNetworks.length === 1 ? options.configuredNetworks[0] : options.configuredNetworks.join(", ") || "indisponible",
backfillTransportReady: options.transportReady ? "ready" : "non ready",
backfillStoreReady: options.storeReady ? "ready" : "non ready",
backfillCompositionReady: options.compositionReady ? "ready" : "non ready",
};
for (const [id, value] of Object.entries(values)) {
const element = document.querySelector<HTMLElement>(`#${id}`);
if (element) {
element.textContent = value;
}
}
const diagnostic = document.querySelector<HTMLElement>("#backfillOptionsDiagnostic");
if (diagnostic) {
const error = options.transportDiagnostic ?? options.storeDiagnostic;
diagnostic.hidden = error === null;
diagnostic.textContent = error === null ? "" : `${error.domain}/${error.code}: ${error.message}`;
}
frontendTrace("main", "Backfill Desk HTTP route options rendered", {
compositionReady: options.compositionReady,
routeCount: options.httpRoutes.length,
transportReady: options.transportReady,
});
}
async function loadBackfillOptions(source: "startup" | "user"): Promise<void> {
frontendDebug("main", "Backfill Desk HTTP route options load started", { source });
const options = await invokeKsp<BackfillDeskOptionsDto>("main", "backfill_options");
renderBackfillOptions(options);
frontendDebug("main", "Backfill Desk HTTP route options load completed", { source, routeCount: options.httpRoutes.length });
}
function bindBackfillRouteSelection(): void {
const select = document.querySelector<HTMLSelectElement>("#backfillHttpRoute");
if (!select) {
return;
}
select.addEventListener("change", () => {
frontendDebug("main", "Backfill Desk HTTP route selection changed", { role: select.value || "none" });
});
frontendTrace("main", "Backfill Desk HTTP route selection handler installed");
}
function renderRuntimeStatus(status: ShellStatusDto): void { function renderRuntimeStatus(status: ShellStatusDto): void {
const values: Record<string, string> = { const values: Record<string, string> = {
runtimeVersion: status.applicationVersion, runtimeVersion: status.applicationVersion,
@@ -131,9 +201,10 @@ async function initializeMain(): Promise<void> {
bindFrontendInteractions(); bindFrontendInteractions();
bindNavigation(); bindNavigation();
bindRuntimeStatusRefresh(); bindRuntimeStatusRefresh();
bindBackfillRouteSelection();
activateView("backfill", "startup"); activateView("backfill", "startup");
try { try {
await loadRuntimeStatus("startup"); await Promise.all([loadRuntimeStatus("startup"), loadBackfillOptions("startup")]);
} catch { } catch {
frontendWarn("main", "Backfill Desk startup runtime status load failed"); frontendWarn("main", "Backfill Desk startup runtime status load failed");
} }

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-backfill-desk/src/app_state.rs // file: crates/ksp-app-backfill-desk/src/app_state.rs
// version: 3 // version: 4
//! Shared backend state owned by the Backfill Desk Tauri application. //! Shared backend state owned by the Backfill Desk Tauri application.
@@ -121,7 +121,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.005-store-readiness".to_owned(), shell_phase: "pre.006-mainnet-http-routing".to_owned(),
startup_diagnostic: runtime.startup_diagnostic.clone(), startup_diagnostic: runtime.startup_diagnostic.clone(),
}); });
} }
@@ -132,7 +132,7 @@ impl crate::AppState {
let options = match runtime { let options = match runtime {
std::option::Option::Some(value) => value.options(), std::option::Option::Some(value) => value.options(),
std::option::Option::None => std::result::Result::Ok(crate::BackfillDeskOptionsDto { std::option::Option::None => std::result::Result::Ok(crate::BackfillDeskOptionsDto {
compatible_roles: std::vec::Vec::new(), http_routes: std::vec::Vec::new(),
composition_ready: false, composition_ready: false,
configured_networks: std::vec::Vec::new(), configured_networks: std::vec::Vec::new(),
network_coherent: false, network_coherent: false,

View File

@@ -1,20 +1,35 @@
// file: crates/ksp-app-backfill-desk/src/dto_common.rs // file: crates/ksp-app-backfill-desk/src/dto_common.rs
// version: 3 // version: 4
//! Common Tauri DTOs shared by the Backfill Desk shell. //! Common Tauri DTOs shared by the Backfill Desk shell.
use ts_rs::TS; // rust-rules: trait-import use ts_rs::TS; // rust-rules: trait-import
/// Safe Transport-readiness subset of the Backfill Desk options contract. /// Safe HTTP route exposed for operator selection before a Backfill run.
/// ///
/// Campaign scopes, commitments and backend-owned bounds are added in the dedicated request/DTO slice. Endpoint URLs, provider identities and credentials /// The route is identified only by the logical Transport role. Provider labels are safe metadata used for operator choice; endpoint URLs and credentials never
/// are intentionally absent. /// cross IPC.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_backfill_desk/dto_common/BackfillHttpRouteOptionDto.ts")]
pub(crate) struct BackfillHttpRouteOptionDto {
/// Whether more than one configured provider participates in this logical route.
pub(crate) pooled: bool,
/// Safe provider labels participating in the route, sorted and deduplicated.
pub(crate) providers: std::vec::Vec<String>,
/// Logical Transport role later passed to the Backfill runtime.
pub(crate) role: String,
}
/// Safe Transport-and-Store readiness subset of the Backfill Desk options contract.
///
/// Campaign scopes, commitments and backend-owned bounds are added in the dedicated request/DTO slice. Endpoint URLs and credentials are intentionally absent.
#[derive(Clone, Debug, serde::Serialize, TS)] #[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_backfill_desk/dto_common/BackfillDeskOptionsDto.ts")] #[ts(export, export_to = "../frontend/ts/bindings/ksp_app_backfill_desk/dto_common/BackfillDeskOptionsDto.ts")]
pub(crate) struct BackfillDeskOptionsDto { pub(crate) struct BackfillDeskOptionsDto {
/// Logical roles that can route both RPC methods required by the Backfill runtime. /// Selectable logical HTTP routes that support both RPC methods required by the Backfill runtime.
pub(crate) compatible_roles: std::vec::Vec<String>, pub(crate) http_routes: std::vec::Vec<BackfillHttpRouteOptionDto>,
/// Whether the Transport and Store readiness gates jointly permit later Backfill composition. /// Whether the Transport and Store readiness gates jointly permit later Backfill composition.
pub(crate) composition_ready: bool, pub(crate) composition_ready: bool,
/// Distinct enabled HTTP cluster labels selected by the active Transport profile. /// Distinct enabled HTTP cluster labels selected by the active Transport profile.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-backfill-desk/src/lib.rs // file: crates/ksp-app-backfill-desk/src/lib.rs
// version: 4 // version: 5
//! Tauri desktop application scaffold for controlling and inspecting KSP RAW backfill jobs. //! Tauri desktop application scaffold for controlling and inspecting KSP RAW backfill jobs.
@@ -62,8 +62,10 @@ pub(crate) use self::constants::TRACING_TARGET_FRONTEND;
pub(crate) use self::constants::TRACING_TARGET_FRONTEND_MAIN; pub(crate) use self::constants::TRACING_TARGET_FRONTEND_MAIN;
/// Owning target for splash-window frontend events. /// Owning target for splash-window frontend events.
pub(crate) use self::constants::TRACING_TARGET_FRONTEND_SPLASH; pub(crate) use self::constants::TRACING_TARGET_FRONTEND_SPLASH;
/// Safe Transport-readiness subset of the Backfill Desk options contract. /// Safe Transport-and-Store readiness subset of the Backfill Desk options contract.
pub(crate) use self::dto_common::BackfillDeskOptionsDto; pub(crate) use self::dto_common::BackfillDeskOptionsDto;
/// Safe logical HTTP route exposed for operator selection.
pub(crate) use self::dto_common::BackfillHttpRouteOptionDto;
/// Safe command error projection exposed to Tauri commands. /// Safe command error projection exposed to Tauri commands.
pub(crate) use self::dto_common::CommandErrorDto; pub(crate) use self::dto_common::CommandErrorDto;
/// Safe scaffold/runtime snapshot exposed to the shell. /// Safe scaffold/runtime snapshot exposed to the shell.

View File

@@ -1,7 +1,7 @@
// file: crates/ksp-app-backfill-desk/src/transport_runtime.rs // file: crates/ksp-app-backfill-desk/src/transport_runtime.rs
// version: 2 // version: 3
//! Composite-selected HTTP Transport readiness owned by Backfill Desk. //! Composite-selected HTTP Transport readiness and route inventory owned by Backfill Desk.
/// Safe and executable Transport runtime retained by the application state. /// Safe and executable Transport runtime retained by the application state.
pub(crate) struct TransportRuntime { pub(crate) struct TransportRuntime {
@@ -30,16 +30,16 @@ impl TransportRuntime {
pub(crate) fn options(&self) -> ksp_core_lib::Result<crate::BackfillDeskOptionsDto> { pub(crate) fn options(&self) -> ksp_core_lib::Result<crate::BackfillDeskOptionsDto> {
let snapshot = self.pool.snapshot(); let snapshot = self.pool.snapshot();
let configured_networks = configured_networks(&snapshot); let configured_networks = configured_networks(&snapshot);
let compatible_roles = compatible_backfill_roles(&self.pool, &snapshot); let http_routes = compatible_backfill_http_routes(&self.pool, &snapshot);
let compatible_roles = match compatible_roles { let http_routes = match http_routes {
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),
}; };
let transport_ready = configured_networks.len() == 1 && !compatible_roles.is_empty() && snapshot.available_endpoint_count() > 0; let transport_ready = configured_networks.len() == 1 && !http_routes.is_empty() && snapshot.available_endpoint_count() > 0;
return std::result::Result::Ok(crate::BackfillDeskOptionsDto { return std::result::Result::Ok(crate::BackfillDeskOptionsDto {
compatible_roles,
composition_ready: false, composition_ready: false,
configured_networks, configured_networks,
http_routes,
network_coherent: false, network_coherent: false,
store_diagnostic: std::option::Option::None, store_diagnostic: std::option::Option::None,
store_network: std::option::Option::None, store_network: std::option::Option::None,
@@ -89,17 +89,17 @@ pub(crate) fn initialize_transport(management: &ksp_config_lib::ConfigManagement
domain = crate::TRACING_DOMAIN_TRANSPORT, domain = crate::TRACING_DOMAIN_TRANSPORT,
transport_profile = runtime.profile_id(), transport_profile = runtime.profile_id(),
network_count = options.configured_networks.len(), network_count = options.configured_networks.len(),
compatible_role_count = options.compatible_roles.len(), http_route_count = options.http_routes.len(),
transport_ready = options.transport_ready, transport_ready = options.transport_ready,
"initialized Backfill Desk HTTP Transport readiness from composite-managed configuration" "initialized Backfill Desk HTTP Transport readiness from composite-managed configuration"
); );
return std::result::Result::Ok(runtime); return std::result::Result::Ok(runtime);
} }
fn compatible_backfill_roles( fn compatible_backfill_http_routes(
pool: &ksp_onchain_transport_lib::HttpTransportPool, pool: &ksp_onchain_transport_lib::HttpTransportPool,
snapshot: &ksp_onchain_transport_lib::HttpTransportPoolSnapshot, snapshot: &ksp_onchain_transport_lib::HttpTransportPoolSnapshot,
) -> ksp_core_lib::Result<std::vec::Vec<String>> { ) -> ksp_core_lib::Result<std::vec::Vec<crate::BackfillHttpRouteOptionDto>> {
let signatures = required_http_rpc_method("getSignaturesForAddress"); let signatures = required_http_rpc_method("getSignaturesForAddress");
let signatures = match signatures { let signatures = match signatures {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
@@ -110,22 +110,23 @@ fn compatible_backfill_roles(
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),
}; };
let mut candidates = std::collections::BTreeSet::new(); let mut candidates = std::collections::BTreeMap::<String, std::collections::BTreeSet<String>>::new();
for endpoint in snapshot.endpoints() { for endpoint in snapshot.endpoints() {
if !endpoint.enabled() { if !endpoint.enabled() {
continue; continue;
} }
for role in endpoint.roles() { for role in endpoint.roles() {
if role.enabled() { if role.enabled() {
candidates.insert(role.role().to_owned()); candidates.entry(role.role().to_owned()).or_default().insert(endpoint.provider().to_owned());
} }
} }
} }
let mut compatible = std::vec::Vec::new(); let mut compatible = std::vec::Vec::new();
for candidate in candidates { for (candidate, providers) in candidates {
let role = ksp_onchain_transport_lib::HttpRoleName::new(candidate.clone()); let role = ksp_onchain_transport_lib::HttpRoleName::new(candidate.clone());
if pool.select_for_method(&role, signatures).is_ok() && pool.select_for_method(&role, transaction).is_ok() { if pool.select_for_method(&role, signatures).is_ok() && pool.select_for_method(&role, transaction).is_ok() {
compatible.push(candidate); let providers = providers.into_iter().collect::<std::vec::Vec<_>>();
compatible.push(crate::BackfillHttpRouteOptionDto { pooled: providers.len() > 1, providers, role: candidate });
} }
} }
return std::result::Result::Ok(compatible); return std::result::Result::Ok(compatible);

View File

@@ -1,7 +1,7 @@
// file: crates/ksp-app-backfill-desk/tests/config_composition.rs // file: crates/ksp-app-backfill-desk/tests/config_composition.rs
// version: 4 // version: 5
//! Config composite, Transport readiness and Store-network composition contracts for Backfill Desk through pre.005. //! Config composite, Transport readiness and Store-network composition contracts for Backfill Desk through pre.006.
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
#![deny(unreachable_pub)] #![deny(unreachable_pub)]
@@ -32,7 +32,7 @@ fn assert_component(composite: &ksp_config_lib::ResolvedConfigComposite, compone
} }
#[test] #[test]
fn pre_003_default_composite_selects_trace_devnet_logging_transport_and_store() { fn pre_003_devnet_profile_selects_trace_logging_transport_and_store() {
let engine = workspace_engine(); let engine = workspace_engine();
assert!(engine.is_ok(), "workspace Config engine should be constructible: {engine:?}"); assert!(engine.is_ok(), "workspace Config engine should be constructible: {engine:?}");
let engine = match engine { let engine = match engine {
@@ -45,8 +45,8 @@ fn pre_003_default_composite_selects_trace_devnet_logging_transport_and_store()
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return, std::result::Result::Err(_) => return,
}; };
let composite = engine.load_resolved_composite(&file_id, std::option::Option::None); let composite = engine.load_resolved_composite(&file_id, std::option::Option::Some("devnet"));
assert!(composite.is_ok(), "committed Backfill Desk composite should resolve: {composite:?}"); assert!(composite.is_ok(), "committed Backfill Desk devnet profile should resolve: {composite:?}");
if let std::result::Result::Ok(composite) = composite { if let std::result::Result::Ok(composite) = composite {
assert_eq!(composite.profile_id(), "devnet"); assert_eq!(composite.profile_id(), "devnet");
assert_component(&composite, "logging", ksp_config_lib::FILE_ID_STD_LOGGING, "supertrace"); assert_component(&composite, "logging", ksp_config_lib::FILE_ID_STD_LOGGING, "supertrace");
@@ -74,7 +74,7 @@ fn pre_003_named_profiles_keep_transport_and_store_network_selection_paired() {
std::result::Result::Err(_) => return, std::result::Result::Err(_) => return,
}; };
for (profile_id, logging_profile, transport_profile, store_profile) in for (profile_id, logging_profile, transport_profile, store_profile) in
[("mainnet", "console_info", "mainnet_public", "mainnet"), ("testnet", "console_info", "publicnode_testnet", "testnet")] [("mainnet", "supertrace", "mainnet_backfill_pool", "mainnet"), ("testnet", "console_info", "publicnode_testnet", "testnet")]
{ {
let composite = engine.load_resolved_composite(&file_id, std::option::Option::Some(profile_id)); let composite = engine.load_resolved_composite(&file_id, std::option::Option::Some(profile_id));
assert!(composite.is_ok(), "Backfill Desk composite profile {profile_id} should resolve: {composite:?}"); assert!(composite.is_ok(), "Backfill Desk composite profile {profile_id} should resolve: {composite:?}");
@@ -100,8 +100,8 @@ fn pre_004_default_composite_builds_http_pool_for_both_backfill_rpc_methods() {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return, std::result::Result::Err(_) => return,
}; };
let composite = engine.load_resolved_composite(&file_id, std::option::Option::None); let composite = engine.load_resolved_composite(&file_id, std::option::Option::Some("devnet"));
assert!(composite.is_ok(), "committed Backfill Desk composite should resolve: {composite:?}"); assert!(composite.is_ok(), "committed Backfill Desk devnet profile should resolve: {composite:?}");
let composite = match composite { let composite = match composite {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return, std::result::Result::Err(_) => return,
@@ -215,3 +215,52 @@ fn pre_005_all_composite_profiles_keep_declared_http_and_store_networks_identica
} }
} }
} }
#[test]
fn pre_006_default_mainnet_profile_exposes_pool_and_targeted_http_routes_without_secret_provider_data() {
let engine = workspace_engine();
assert!(engine.is_ok(), "workspace Config engine should be constructible: {engine:?}");
let engine = match engine {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let file_id = ksp_config_lib::ConfigFileId::new(ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_BACKFILL_DESK);
let file_id = match file_id {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let composite = engine.load_resolved_composite(&file_id, std::option::Option::None);
assert!(composite.is_ok(), "default Backfill Desk composite should resolve: {composite:?}");
let composite = match composite {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(composite.profile_id(), "mainnet");
assert_component(&composite, "logging", ksp_config_lib::FILE_ID_STD_LOGGING, "supertrace");
assert_component(&composite, "transport", ksp_config_lib::FILE_ID_STD_TRANSPORT, "mainnet_backfill_pool");
assert_component(&composite, "store", ksp_config_lib::FILE_ID_STD_STORE, "mainnet");
let transport = match composite.component("transport") {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let profile = transport.resolved().profile();
let endpoints = profile.get("endpoints").and_then(serde_json::Value::as_array);
assert!(endpoints.is_some(), "mainnet Backfill Transport profile should expose HTTP endpoints");
let endpoints = match endpoints {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let providers = endpoints
.iter()
.filter_map(|endpoint| endpoint.get("provider").and_then(serde_json::Value::as_str))
.collect::<std::collections::BTreeSet<_>>();
assert_eq!(providers, std::collections::BTreeSet::from(["publicnode", "solana-public"]));
let roles = endpoints
.iter()
.flat_map(|endpoint| endpoint.get("roles").and_then(serde_json::Value::as_array).into_iter().flatten())
.filter_map(|role| role.get("role").and_then(serde_json::Value::as_str))
.collect::<std::collections::BTreeSet<_>>();
assert!(roles.contains("backfill_pool"));
assert!(roles.contains("backfill_publicnode"));
assert!(roles.contains("backfill_solana_public"));
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-backfill-desk/tests/desktop_contract.rs // file: crates/ksp-app-backfill-desk/tests/desktop_contract.rs
// version: 5 // version: 6
//! Structural desktop contract checks for the Backfill Desk scaffold. //! Structural desktop contract checks for the Backfill Desk scaffold.
@@ -193,8 +193,9 @@ fn pre_004_transport_runtime_builds_pool_and_validates_both_required_rpc_methods
assert!(transport.contains("getTransaction")); assert!(transport.contains("getTransaction"));
assert!(transport.contains("select_for_method")); assert!(transport.contains("select_for_method"));
assert!(transport.contains("configured_networks")); assert!(transport.contains("configured_networks"));
assert!(transport.contains("compatible_backfill_roles")); assert!(transport.contains("compatible_backfill_http_routes"));
for forbidden in ["provider()", "url()", "Store::open", "BackfillJobRuntime", "BackfillRequest"] { assert!(transport.contains("endpoint.provider()"));
for forbidden in ["url()", "Store::open", "BackfillJobRuntime", "BackfillRequest"] {
assert!(!transport.contains(forbidden), "pre.004 opens or projects forbidden surface {forbidden}"); assert!(!transport.contains(forbidden), "pre.004 opens or projects forbidden surface {forbidden}");
} }
let tauri = read_text(root.join("src/tauri.rs").as_path()); let tauri = read_text(root.join("src/tauri.rs").as_path());
@@ -220,7 +221,6 @@ fn pre_005_store_readiness_proves_network_before_open_and_closes_on_main_window_
assert!(!store.contains("BackfillRequest")); assert!(!store.contains("BackfillRequest"));
let app_state = read_text(root.join("src/app_state.rs").as_path()); let app_state = read_text(root.join("src/app_state.rs").as_path());
assert!(app_state.contains("initialize_store")); assert!(app_state.contains("initialize_store"));
assert!(app_state.contains("pre.005-store-readiness"));
assert!(app_state.contains("composition_ready")); assert!(app_state.contains("composition_ready"));
assert!(app_state.contains("close_store")); assert!(app_state.contains("close_store"));
let tauri = read_text(root.join("src/tauri.rs").as_path()); let tauri = read_text(root.join("src/tauri.rs").as_path());
@@ -228,3 +228,26 @@ fn pre_005_store_readiness_proves_network_before_open_and_closes_on_main_window_
assert!(tauri.contains(required), "missing bounded Store shutdown lifecycle marker {required}"); assert!(tauri.contains(required), "missing bounded Store shutdown lifecycle marker {required}");
} }
} }
#[test]
fn pre_006_mainnet_default_and_http_route_selector_are_explicit_without_start_runtime() {
let root = app_root();
let workspace = root.join("../..");
let composite = read_text(workspace.join("config/composite.ksp-app-backfill-desk.json").as_path());
assert!(composite.contains("\"default_profile\": \"mainnet\""));
assert!(composite.contains("\"profile_id\": \"mainnet_backfill_pool\""));
let transport = read_text(workspace.join("config/std.transport.json").as_path());
for required in ["mainnet_backfill_pool", "backfill_pool", "backfill_publicnode", "backfill_solana_public", "solana-rpc.publicnode.com"] {
assert!(transport.contains(required), "missing Backfill HTTP route marker {required}");
}
let app_state = read_text(root.join("src/app_state.rs").as_path());
assert!(app_state.contains("pre.006-mainnet-http-routing"));
let frontend = read_text(root.join("frontend/ts/main.ts").as_path());
assert!(frontend.contains("Backfill Desk HTTP route selection changed"));
assert!(frontend.contains("backfill_options"));
let html = read_text(root.join("frontend/main.html").as_path());
assert!(html.contains("backfillHttpRoute"));
for forbidden in ["backfill_start", "BackfillJobRuntime", "grpc", "websocket"] {
assert!(!frontend.contains(forbidden), "pre.006 frontend opens deferred runtime marker {forbidden}");
}
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-backfill-desk/tests/desktop_security.rs // file: crates/ksp-app-backfill-desk/tests/desktop_security.rs
// version: 5 // version: 6
//! Security and dependency-boundary checks for the Backfill Desk scaffold. //! Security and dependency-boundary checks for the Backfill Desk scaffold.
@@ -137,7 +137,23 @@ fn pre_005_opens_store_facade_without_backend_or_job_dependencies() {
} }
let dto = read_text(root.join("src/dto_common.rs").as_path()); let dto = read_text(root.join("src/dto_common.rs").as_path());
assert!(dto.contains("BackfillDeskOptionsDto")); assert!(dto.contains("BackfillDeskOptionsDto"));
for forbidden in ["pub(crate) provider", "endpoint_url", "pub(crate) url", "pub(crate) credential", "pub(crate) token", "connection_uri"] { for forbidden in ["endpoint_url", "pub(crate) url", "pub(crate) credential", "pub(crate) token", "connection_uri"] {
assert!(!dto.contains(forbidden), "readiness options DTO source contains forbidden field marker {forbidden}"); assert!(!dto.contains(forbidden), "readiness options DTO source contains forbidden field marker {forbidden}");
} }
} }
#[test]
fn pre_006_frontend_receives_only_safe_http_route_metadata_and_no_endpoint_material() {
let root = app_root();
let dto = read_text(root.join("src/dto_common.rs").as_path());
assert!(dto.contains("BackfillHttpRouteOptionDto"));
assert!(dto.contains("pub(crate) providers"));
assert!(dto.contains("pub(crate) role"));
for forbidden in ["endpoint_url", "pub(crate) url", "pub(crate) credential", "pub(crate) api_key", "pub(crate) authorization"] {
assert!(!dto.contains(forbidden), "route DTO leaks forbidden endpoint material marker {forbidden}");
}
let frontend = read_text(root.join("frontend/ts/main.ts").as_path());
for forbidden in ["http://", "https://", "apiKey", "authorization", "token"] {
assert!(!frontend.contains(forbidden), "frontend embeds forbidden provider material marker {forbidden}");
}
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-backfill-desk/unit_tests/dto_common.rs // file: crates/ksp-app-backfill-desk/unit_tests/dto_common.rs
// version: 3 // version: 4
#[test] #[test]
fn command_error_projection_excludes_arbitrary_context_and_source_values() { fn command_error_projection_excludes_arbitrary_context_and_source_values() {
@@ -16,7 +16,7 @@ fn command_error_projection_excludes_arbitrary_context_and_source_values() {
#[test] #[test]
fn transport_options_projection_contains_only_safe_transport_metadata() { fn transport_options_projection_contains_only_safe_transport_metadata() {
let dto = crate::BackfillDeskOptionsDto { let dto = crate::BackfillDeskOptionsDto {
compatible_roles: vec!["default".to_owned()], http_routes: vec![crate::BackfillHttpRouteOptionDto { pooled: false, providers: vec!["solana-public".to_owned()], role: "default".to_owned() }],
composition_ready: true, composition_ready: true,
configured_networks: vec!["devnet".to_owned()], configured_networks: vec!["devnet".to_owned()],
network_coherent: true, network_coherent: true,
@@ -32,7 +32,7 @@ fn transport_options_projection_contains_only_safe_transport_metadata() {
let serialized = value.to_string(); let serialized = value.to_string();
assert!(serialized.contains("default")); assert!(serialized.contains("default"));
assert!(serialized.contains("devnet")); assert!(serialized.contains("devnet"));
assert!(!serialized.contains("provider")); assert!(serialized.contains("solana-public"));
assert!(!serialized.contains("url")); assert!(!serialized.contains("url"));
assert!(!serialized.contains("endpoint")); assert!(!serialized.contains("endpoint"));
assert!(!serialized.contains("postgres")); assert!(!serialized.contains("postgres"));

View File

@@ -46,7 +46,7 @@ fn pool(endpoints: std::vec::Vec<ksp_onchain_transport_lib::HttpEndpointSettings
} }
#[test] #[test]
fn compatible_roles_require_both_backfill_rpc_methods() { fn http_routes_require_both_backfill_rpc_methods() {
let signatures = ksp_onchain_transport_lib::find_http_rpc_method("getSignaturesForAddress"); let signatures = ksp_onchain_transport_lib::find_http_rpc_method("getSignaturesForAddress");
let transaction = ksp_onchain_transport_lib::find_http_rpc_method("getTransaction"); let transaction = ksp_onchain_transport_lib::find_http_rpc_method("getTransaction");
assert!(signatures.is_some()); assert!(signatures.is_some());
@@ -76,10 +76,15 @@ fn compatible_roles_require_both_backfill_rpc_methods() {
std::result::Result::Err(_) => return, std::result::Result::Err(_) => return,
}; };
let snapshot = runtime_pool.snapshot(); let snapshot = runtime_pool.snapshot();
let roles = super::compatible_backfill_roles(&runtime_pool, &snapshot); let roles = super::compatible_backfill_http_routes(&runtime_pool, &snapshot);
assert!(roles.is_ok()); assert!(roles.is_ok());
if let std::result::Result::Ok(roles) = roles { if let std::result::Result::Ok(roles) = roles {
assert_eq!(roles, vec!["complete".to_owned()]); assert_eq!(roles.len(), 1);
if let std::option::Option::Some(route) = roles.first() {
assert_eq!(route.role, "complete");
assert_eq!(route.providers, vec!["fixture".to_owned()]);
assert!(!route.pooled);
}
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"format_version": 1, "format_version": 1,
"default_profile": "devnet", "default_profile": "mainnet",
"profiles": [ "profiles": [
{ {
"profile_id": "devnet", "profile_id": "devnet",

66
deltas/0.3.7/pre.006.md Normal file
View File

@@ -0,0 +1,66 @@
# Delta `0.3.7-pre.006`
## Base requise
`0.3.7-pre.005-fix.001`.
## Objectif
Faire de Mainnet le profil Backfill Desk par défaut et introduire une sélection HTTP opérateur entre pool multi-provider et provider ciblé, sans ouvrir encore le runtime Job/Start.
## Fichiers ajoutés
- `deltas/0.3.7/pre.006.md`
## Fichiers modifiés
- `Cargo.toml`
- `config/composite.ksp-app-backfill-desk.json`
- `config/std.transport.json`
- `crates/ksp-config-lib/unit_tests/fixtures/composite.ksp-app-backfill-desk.json`
- `crates/ksp-app-backfill-desk/src/app_state.rs`
- `crates/ksp-app-backfill-desk/src/dto_common.rs`
- `crates/ksp-app-backfill-desk/src/lib.rs`
- `crates/ksp-app-backfill-desk/src/transport_runtime.rs`
- `crates/ksp-app-backfill-desk/unit_tests/dto_common.rs`
- `crates/ksp-app-backfill-desk/unit_tests/transport_runtime.rs`
- `crates/ksp-app-backfill-desk/tests/config_composition.rs`
- `crates/ksp-app-backfill-desk/tests/desktop_contract.rs`
- `crates/ksp-app-backfill-desk/tests/desktop_security.rs`
- `crates/ksp-app-backfill-desk/frontend/main.html`
- `crates/ksp-app-backfill-desk/frontend/ts/main.ts`
- `docs/plans/028-V0_3_7_BACKFILL_DESK_PLAN.md`
- `docs/validation/024-V0_3_7_BACKFILL_DESK.md`
## Fichiers supprimés
Aucun.
## Décisions
- Mainnet devient le profil composite par défaut ; Devnet/Testnet restent configurables.
- Backfill reste HTTP-only en `0.3.7`.
- `backfill_pool` route Solana Public + PublicNode ; deux rôles dédiés permettent le ciblage d'un provider.
- Le frontend ne choisit jamais une URL : il choisit un rôle logique Transport.
- Helius HTTP peut être ajouté ultérieurement au même mécanisme de rôles via Config/Transport, sans modifier le contrat Job.
- Yellowstone gRPC est reporté à une version ultérieure (`0.3.12+` indicatif) ; WebSocket reste hors scope.
## Validations exécutées dans l'environnement d'assemblage
- audits Python Rust/workspace ;
- audit Markdown ;
- parsing JSON/TOML ;
- validation JSON Schema du Transport/composite ;
- contrôles statiques Mainnet/default/rôles HTTP ;
- contrôle TypeScript syntaxique avec bindings temporaires ;
- contrôle différentiel et reconstruction par delta.
## Validations non exécutées
`cargo`, `rustfmt`, Clippy et les tests Rust ne sont pas disponibles dans l'environnement d'assemblage. Ils restent obligatoires côté opérateur avant `pre.007`.
## Questions ouvertes
- admission et mapping complet `BackfillRequest` en `pre.007` ;
- Helius HTTP ne doit être activé qu'avec une configuration de secret réellement disponible ;
- smoke Mainnet lecture seule après intégration du Start, pas avant.

View File

@@ -137,16 +137,16 @@ Aucun `BackfillCheckpoint`, `Store`, `HttpTransportPool`, sender/receiver Tokio
Le composite cible est `config/composite.ksp-app-backfill-desk.json` avec trois profils cohérents : Le composite cible est `config/composite.ksp-app-backfill-desk.json` avec trois profils cohérents :
| Profil composite | Logging | Transport | Store | Réseau | | Profil composite | Logging | Transport | Store | Réseau |
|------------------|----------------|----------------------|-----------|----------------| |------------------|----------------|-------------------------|-----------|----------------|
| `devnet` | `supertrace` | `devnet_public` | `devnet` | `devnet` | | `devnet` | `supertrace` | `devnet_public` | `devnet` | `devnet` |
| `mainnet` | `console_info` | `mainnet_public` | `mainnet` | `mainnet-beta` | | `mainnet` | `supertrace` | `mainnet_backfill_pool` | `mainnet` | `mainnet-beta` |
| `testnet` | `console_info` | `publicnode_testnet` | `testnet` | `testnet` | | `testnet` | `console_info` | `publicnode_testnet` | `testnet` | `testnet` |
Le default V1 est `devnet`, afin de ne pas lancer implicitement une campagne mainnet. Aucun `std.job_backfill.json` n'est créé : les paramètres de campagne restent opérationnels et backend-validés. Le default V1 est désormais `mainnet`, conformément à la finalité lecture seule du Backfill Desk. `devnet` et `testnet` restent sélectionnables par reconfiguration du composite. Pendant le développement de `0.3.7`, Mainnet conserve le profil Logging `supertrace`. Aucun `std.job_backfill.json` n'est créé : les paramètres de campagne restent opérationnels et backend-validés.
Au bootstrap, l'application charge le composite, résout Logging/Transport/Store via `ksp-config-lib`, construit `HttpTransportPool` et ouvre `ksp_store_lib::Store`. Elle compare le réseau Store au(x) cluster(s) HTTP activés et refuse le readiness si une composition mélange des réseaux. Au bootstrap, l'application charge le composite, résout Logging/Transport/Store via `ksp-config-lib`, construit `HttpTransportPool` et ouvre `ksp_store_lib::Store`. Elle compare le réseau Store au(x) cluster(s) HTTP activés et refuse le readiness si une composition mélange des réseaux.
Les rôles présentés à l'UI sont dérivés des snapshots/settings Transport, dédupliqués, puis validés côté Rust comme capables de sélectionner à la fois `getSignaturesForAddress` et `getTransaction`. Le rôle `default` est actuellement le seul rôle des profils standard, mais il n'est pas codé en dur comme contrat du Desk. Les routes HTTP présentées à l'UI sont dérivées des snapshots/settings Transport, dédupliquées, puis validées côté Rust comme capables de sélectionner à la fois `getSignaturesForAddress` et `getTransaction`. Sur Mainnet, `backfill_pool` regroupe Solana Public et PublicNode à priorité égale, tandis que `backfill_solana_public` et `backfill_publicnode` ciblent un provider unique. Le frontend ne reçoit que le rôle logique et les labels provider sûrs ; URL, credentials, rate-limit interne et retry restent possédés par Transport.
Le packaging Config suit le contrat atomique déjà détenu par `ksp-config-lib::prepare_packaged_runtime()` : il traverse l'intégralité de `ConfigFileRegistry::defaults()`. L'ajout du composite Backfill Desk fait donc passer le registre de 13 à 14 descriptors et impose que les quatre applications Desk qui utilisent ce packaging embarquent les 14 resources Config/schema du registre. Backfill Desk embarque ainsi également les documents Wallet/offchain sans les consommer au runtime ; leur présence satisfait le contrat de packaging partagé et n'ouvre aucune dépendance, permission frontend ou responsabilité métier supplémentaire. Le packaging Config suit le contrat atomique déjà détenu par `ksp-config-lib::prepare_packaged_runtime()` : il traverse l'intégralité de `ConfigFileRegistry::defaults()`. L'ajout du composite Backfill Desk fait donc passer le registre de 13 à 14 descriptors et impose que les quatre applications Desk qui utilisent ce packaging embarquent les 14 resources Config/schema du registre. Backfill Desk embarque ainsi également les documents Wallet/offchain sans les consommer au runtime ; leur présence satisfait le contrat de packaging partagé et n'ouvre aucune dépendance, permission frontend ou responsabilité métier supplémentaire.
@@ -202,7 +202,7 @@ Aucun JSON, fichier ou table checkpoint n'est inventé. La reprise après crash/
| Menace | Surface | Contrôle v0.3.7 | | Menace | Surface | Contrôle v0.3.7 |
|------------------------------|------------------|----------------------------------------------------------------------------------------------| |------------------------------|------------------|----------------------------------------------------------------------------------------------|
| fuite URI Store | Config/backend | jamais projetée en DTO/log frontend | | fuite URI Store | Config/backend | jamais projetée en DTO/log frontend |
| fuite URL/token provider | Transport/Config | uniquement rôle/cluster/readiness sûrs ; aucune URL | | fuite URL/token provider | Transport/Config | rôle + labels provider sûrs uniquement ; aucune URL/credential |
| fuite RAW transaction | snapshots/IPC | snapshot existant ne contient aucun payload ; aucun Store read général | | fuite RAW transaction | snapshots/IPC | snapshot existant ne contient aucun payload ; aucun Store read général |
| fuite liste signatures | request/logging | input transmis au command, non journalisé ; DTO retour n'en contient pas | | fuite liste signatures | request/logging | input transmis au command, non journalisé ; DTO retour n'en contient pas |
| fuite adresse | instrumentation | ne pas journaliser la valeur ; seulement scope kind/action code | | fuite adresse | instrumentation | ne pas journaliser la valeur ; seulement scope kind/action code |
@@ -243,7 +243,7 @@ Le replay opérateur de `pre.003-fix.001` est ensuite intégralement propre : au
### pre.004 — Transport readiness ### pre.004 — Transport readiness
Construire `HttpTransportPool`, dériver l'inventaire sûr des réseaux/rôles, vérifier le support simultané de `getSignaturesForAddress` et `getTransaction`, puis exposer `backfill_options()` sans provider, endpoint physique ni URL. `BackfillDeskOptionsDto` apparaît ici comme sous-contrat Transport seulement (`configured_networks`, rôles compatibles, diagnostic sûr, readiness) ; `pre.006` l'étendra avec commitments, scopes et bornes plutôt que d'ouvrir prématurément `ksp-job-backfill-lib`. Une composition Transport avec plusieurs clusters HTTP actifs ou sans rôle compatible reste non-ready. Une erreur de résolution/construction Transport est conservée comme diagnostic sûr afin que le shell puisse rester ouvrable. Construire `HttpTransportPool`, dériver l'inventaire sûr des réseaux/rôles, vérifier le support simultané de `getSignaturesForAddress` et `getTransaction`, puis exposer `backfill_options()` sans provider, endpoint physique ni URL. `BackfillDeskOptionsDto` apparaît ici comme sous-contrat Transport seulement (`configured_networks`, rôles compatibles, diagnostic sûr, readiness) ; `pre.007` l'étendra avec commitments, scopes et bornes après stabilisation du choix HTTP de `pre.006`. Une composition Transport avec plusieurs clusters HTTP actifs ou sans rôle compatible reste non-ready. Une erreur de résolution/construction Transport est conservée comme diagnostic sûr afin que le shell puisse rester ouvrable.
Le replay opérateur de `pre.004` est acquis : audits, `cargo check --workspace`, Clippy, toutes les suites ciblées Backfill Desk et les deux arbres de dépendances passent. Le lancement `cargo tauri dev` confirme également le profil Logging `supertrace`, le profil Transport `devnet_public`, un réseau HTTP unique, un rôle compatible et `transport_ready = true`; les clics de tabs frontend apparaissent bien au niveau `DEBUG`. Le replay opérateur de `pre.004` est acquis : audits, `cargo check --workspace`, Clippy, toutes les suites ciblées Backfill Desk et les deux arbres de dépendances passent. Le lancement `cargo tauri dev` confirme également le profil Logging `supertrace`, le profil Transport `devnet_public`, un réseau HTTP unique, un rôle compatible et `transport_ready = true`; les clics de tabs frontend apparaissent bien au niveau `DEBUG`.
@@ -253,45 +253,49 @@ Ouvrir uniquement `ksp-store-lib::Store` depuis le profil Store sélectionné pa
Le `Store` ouvert reste détenu côté Rust et doit être fermé explicitement via `Store::close()` lors d'une demande de fermeture de la fenêtre principale. La Desk intercepte cette fermeture une seule fois, empêche la destruction immédiate, lance le close borné puis quitte l'application. Aucun `BackfillRequest`, `BackfillJobRuntime`, accès backend PostgreSQL direct ou capability Tauri supplémentaire n'est admis dans cette tranche. Le `Store` ouvert reste détenu côté Rust et doit être fermé explicitement via `Store::close()` lors d'une demande de fermeture de la fenêtre principale. La Desk intercepte cette fermeture une seule fois, empêche la destruction immédiate, lance le close borné puis quitte l'application. Aucun `BackfillRequest`, `BackfillJobRuntime`, accès backend PostgreSQL direct ou capability Tauri supplémentaire n'est admis dans cette tranche.
### pre.006 — DTO/request mapping ### pre.006 — Mainnet par défaut + sélection route HTTP
Compléter les DTO TS-RS app-owned commencés par le sous-contrat Transport de `pre.004` : quatre scopes, commitments, min context slot, bornes backend-owned, signatures hostiles et validation de rôle/réseau. Faire de `mainnet` le profil composite par défaut, conserver `supertrace` pendant le développement et ajouter un profil `mainnet_backfill_pool` HTTP-only. Exposer dans `backfill_options()` des routes sûres dérivées des rôles Transport : `backfill_pool` pour Solana Public + PublicNode, `backfill_solana_public` et `backfill_publicnode` pour un ciblage explicite. Le frontend rend ce choix avant Start et trace les changements ; aucune URL/clé ne traverse IPC et `ksp-job-backfill-lib` n'est pas encore ouvert par la Desk.
### pre.007 — runtime + Start ### pre.007 — DTO/request mapping
Single-active-run state, construction `BackfillJobRuntime`, spawn non bloquant, handle installé avant exécution et snapshot initial/terminal. Ajouter `ksp-job-backfill-lib` et compléter les DTO TS-RS app-owned : quatre scopes HTTP existants, commitments, min context slot, bornes backend-owned, signatures explicites hostiles et rôle HTTP sélectionné. Mapper strictement vers `BackfillRequest`/`HttpRoleName` côté Rust sans Start effectif et refuser toute route qui n'appartient pas à l'inventaire courant.
### pre.008 — monitoring latest-value ### pre.008 — runtime + Start
Single-active-run state, construction `BackfillJobRuntime`, spawn non bloquant, handle installé avant exécution, admission atomique et snapshot initial/terminal.
### pre.009 — monitoring latest-value
Bridge de snapshots, status DTO, événements coalescés, compteurs complets, resynchronisation et absence de log parsing. Bridge de snapshots, status DTO, événements coalescés, compteurs complets, resynchronisation et absence de log parsing.
### pre.009 — Cancel et races terminales ### pre.010 — Cancel et races terminales
Cancel, état cancelling, idempotence UX, concurrence Start/Cancel/terminal, sémantique de drain Store et fermeture app. Cancel, état cancelling, idempotence UX, concurrence Start/Cancel/terminal, sémantique de drain Store et fermeture app.
### pre.010 — checkpoint/frontier et Resume ### pre.011 — checkpoint/frontier et Resume
Projection sûre, conservation Rust-only du checkpoint, reprise in-session et invariants par scope. Projection sûre, conservation Rust-only du checkpoint, reprise in-session et invariants par scope.
### pre.011 — frontend fonctionnel et polish ### pre.012 — frontend fonctionnel et polish
Formulaire complet, états responsive, instrumentation sûre, summary terminal, erreurs, boutons contextuels, aucun secret/browser storage. Formulaire complet, états responsive, instrumentation sûre, summary terminal, erreurs, boutons contextuels, aucun secret/browser storage.
### pre.012 — hardening et complétude ### pre.013 — hardening et complétude
Tests desktop/security/dependency/composition/release completeness, canaries DTO et scan des dépendances/interdictions. Tests desktop/security/dependency/composition/release completeness, canaries DTO et scan des dépendances/interdictions.
### pre.013 — gate technique final ### pre.014 — gate technique final
`cargo fmt`, audits, check/clippy/tests workspace, arbres Cargo utiles, `cargo tauri build`, smoke opt-in si environnement sûr disponible. `cargo fmt`, audits, check/clippy/tests workspace, arbres Cargo utiles, `cargo tauri build`, smoke Mainnet lecture seule opt-in si environnement sûr disponible.
### pre.014 — réconciliation documentaire ### pre.015 — réconciliation documentaire
README/USAGE/plan/validation et architecture seulement si la surface finale l'exige. Aucun nouveau runtime. README/USAGE/plan/validation et architecture seulement si la surface finale l'exige. Aucun nouveau runtime.
### pre.015 — préparation publication ### pre.016 — préparation publication
Prompt 0.3.8, CHANGELOG et ROADMAP uniquement avec le bump prerelease/delta requis. Prompt suivant + CHANGELOG + ROADMAP uniquement avec le bump prerelease/delta requis.
### rel.001 — publication stable ### rel.001 — publication stable
@@ -300,3 +304,7 @@ Mécanique de publication `v0.3.7`, sans rattrapage.
## 14. Hors périmètre ## 14. Hors périmètre
Store Desk/navigation RAW, Worker API/service daemon, scheduler/registry générique Job, checkpoint durable, retry Job, sélection provider physique, nouveau protocole Transport, migration Store, decoding/materialization, application globale/control desk. Store Desk/navigation RAW, Worker API/service daemon, scheduler/registry générique Job, checkpoint durable, retry Job, sélection provider physique, nouveau protocole Transport, migration Store, decoding/materialization, application globale/control desk.
## 15. Report transport gRPC
Le Backfill `0.3.7` reste HTTP-only. Une généralisation de `ksp-job-backfill-lib` et de Backfill Desk vers Yellowstone gRPC est reportée à une version ultérieure, avec `0.3.12+` comme cible indicative à réévaluer. WebSocket reste hors Backfill tant qu'un besoin historique concret ne le justifie pas.

View File

@@ -266,6 +266,25 @@ Le correctif ne modifie aucun runtime, aucun composite Config, aucun profil Tran
- [X] aucune modification du composite de production ni des settings Transport/Store ; - [X] aucune modification du composite de production ni des settings Transport/Store ;
- [X] aucun secret provider requis par le test déterministe de cohérence réseau ; - [X] aucun secret provider requis par le test déterministe de cohérence réseau ;
- [X] audits statiques Rust/Markdown rejoués dans l'environnement d'assemblage ; - [X] audits statiques Rust/Markdown rejoués dans l'environnement d'assemblage ;
- [ ] replay opérateur `cargo fmt/check/clippy/test` de `pre.005-fix.001` à exécuter. - [X] replay opérateur `cargo fmt/check/clippy/test` de `pre.005-fix.001` propre.
La décision utilisateur de faire de Mainnet le profil Backfill par défaut et de tester plusieurs familles de transport Backfill (HTTP standard, HTTP provider tel que Helius, Yellowstone gRPC ; WebSocket exclu de V1 sauf besoin démontré) appartient à des couloirs Config/Transport antérieurs. Conformément à `VER-LIFECYCLE-010`, elle n'est pas introduite dans ce fix et doit ouvrir une prerelease dédiée après validation du correctif. La décision utilisateur de faire de Mainnet le profil Backfill par défaut et de tester plusieurs familles de transport Backfill (HTTP standard, HTTP provider tel que Helius, Yellowstone gRPC ; WebSocket exclu de V1 sauf besoin démontré) appartient à des couloirs Config/Transport antérieurs. Conformément à `VER-LIFECYCLE-010`, elle n'est pas introduite dans ce fix et doit ouvrir une prerelease dédiée après validation du correctif.
## `0.3.7-pre.006` — Mainnet par défaut et routage HTTP opérateur
La décision post-`pre.005` est appliquée dans une nouvelle tranche dédiée : le composite Backfill Desk utilise `mainnet` par défaut, avec Logging `supertrace` pendant le développement et un profil Transport `mainnet_backfill_pool`. Ce profil expose deux endpoints HTTP sans secret obligatoire au démarrage : Solana Public et PublicNode.
Trois rôles logiques sont proposés : `backfill_pool` pour le pool multi-provider, `backfill_solana_public` et `backfill_publicnode` pour le ciblage explicite. L'IPC expose uniquement le rôle et les labels provider sûrs ; aucune URL ni credential. Le frontend charge `backfill_options()` au démarrage, rend le sélecteur et trace le changement de route. Aucun Start n'est ajouté dans cette tranche.
Le support Yellowstone gRPC de `ksp-job-backfill-lib`/Backfill Desk est retiré du périmètre `0.3.7` et reporté à une version ultérieure (`0.3.12+` comme cible indicative). WebSocket reste hors Backfill V1.
### Gate `pre.006`
- [X] Mainnet est le profil composite par défaut et conserve `supertrace` pendant le développement ;
- [X] le profil `mainnet_backfill_pool` expose Solana Public + PublicNode en HTTP avec rôles pool/ciblés ;
- [X] aucun endpoint gRPC n'est introduit dans ce profil ;
- [X] l'IPC expose uniquement rôle logique et labels provider sûrs ;
- [X] le frontend charge/rend le sélecteur et trace son changement ;
- [X] aucun Start ni dépendance `ksp-job-backfill-lib` n'est ouvert par la Desk ;
- [X] audits statiques Rust/Markdown, JSON Schema et syntaxe TypeScript propres dans l'environnement d'assemblage ;
- [ ] replay opérateur `cargo fmt/check/clippy/test` de `pre.006` à exécuter.