v0.3.7-pre.007
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/src/app_state.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
//! 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(),
|
||||
config_document_count: document_count,
|
||||
fallback_logging_active: runtime.fallback_active,
|
||||
shell_phase: "pre.006-mainnet-http-routing".to_owned(),
|
||||
shell_phase: "pre.007-request-mapping".to_owned(),
|
||||
startup_diagnostic: runtime.startup_diagnostic.clone(),
|
||||
});
|
||||
}
|
||||
@@ -131,17 +131,27 @@ impl crate::AppState {
|
||||
let runtime = self.transport_runtime.as_ref();
|
||||
let options = match runtime {
|
||||
std::option::Option::Some(value) => value.options(),
|
||||
std::option::Option::None => std::result::Result::Ok(crate::BackfillDeskOptionsDto {
|
||||
http_routes: std::vec::Vec::new(),
|
||||
composition_ready: false,
|
||||
configured_networks: std::vec::Vec::new(),
|
||||
network_coherent: false,
|
||||
store_diagnostic: std::option::Option::None,
|
||||
store_network: std::option::Option::None,
|
||||
store_ready: false,
|
||||
transport_diagnostic: self.transport_startup_diagnostic.clone(),
|
||||
transport_ready: false,
|
||||
}),
|
||||
std::option::Option::None => {
|
||||
let limits = crate::backfill_request_limits();
|
||||
let limits = match limits {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
std::result::Result::Ok(crate::BackfillDeskOptionsDto {
|
||||
commitments: crate::backfill_commitment_codes(),
|
||||
composition_ready: false,
|
||||
configured_networks: std::vec::Vec::new(),
|
||||
http_routes: std::vec::Vec::new(),
|
||||
limits,
|
||||
network_coherent: false,
|
||||
scope_kinds: crate::backfill_scope_kind_codes(),
|
||||
store_diagnostic: std::option::Option::None,
|
||||
store_network: std::option::Option::None,
|
||||
store_ready: false,
|
||||
transport_diagnostic: self.transport_startup_diagnostic.clone(),
|
||||
transport_ready: false,
|
||||
})
|
||||
},
|
||||
};
|
||||
let mut options = match options {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -151,6 +161,39 @@ impl crate::AppState {
|
||||
return std::result::Result::Ok(options);
|
||||
}
|
||||
|
||||
/// Validates and maps one frontend campaign request without starting a Backfill Job.
|
||||
pub(crate) fn validate_backfill_request(&self, request: crate::BackfillStartRequestDto) -> ksp_core_lib::Result<crate::BackfillRequestPreviewDto> {
|
||||
let options = self.backfill_options();
|
||||
let options = match options {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let job_id = ksp_job_api::JobId::new("backfill-desk-validation");
|
||||
let job_id = match job_id {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let mapped = crate::map_backfill_request(request, &options, job_id);
|
||||
let mapped = match mapped {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let preview = crate::project_backfill_request(&mapped);
|
||||
let preview = match preview {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
domain = crate::TRACING_DOMAIN_REQUEST,
|
||||
commitment = preview.commitment.as_str(),
|
||||
http_role = preview.http_role.as_str(),
|
||||
scope_kind = preview.scope_kind.as_str(),
|
||||
"validated Backfill Desk campaign request without starting a Job"
|
||||
);
|
||||
return std::result::Result::Ok(preview);
|
||||
}
|
||||
|
||||
/// Marks graceful application shutdown as started and reports whether this caller won the one-shot transition.
|
||||
pub(crate) fn begin_shutdown(&self) -> bool {
|
||||
return self.shutdown_started.compare_exchange(false, true, std::sync::atomic::Ordering::AcqRel, std::sync::atomic::Ordering::Acquire).is_ok();
|
||||
|
||||
313
crates/ksp-app-backfill-desk/src/backfill_request.rs
Normal file
313
crates/ksp-app-backfill-desk/src/backfill_request.rs
Normal file
@@ -0,0 +1,313 @@
|
||||
// file: crates/ksp-app-backfill-desk/src/backfill_request.rs
|
||||
// version: 1
|
||||
|
||||
//! Strict application mapping from frontend campaign DTOs to the KSP Backfill request contract.
|
||||
|
||||
/// Maps one app-owned campaign request to a validated KSP Backfill request without starting a Job.
|
||||
pub(crate) fn map_backfill_request(
|
||||
input: crate::BackfillStartRequestDto,
|
||||
options: &crate::BackfillDeskOptionsDto,
|
||||
job_id: ksp_job_api::JobId,
|
||||
) -> ksp_core_lib::Result<ksp_job_backfill_lib::BackfillRequest> {
|
||||
let composition = validate_composition(options);
|
||||
if let std::result::Result::Err(error) = composition {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let network = request_network(options);
|
||||
let network = match network {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let role = request_role(input.http_role.as_str(), options);
|
||||
let role = match role {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let commitment = request_commitment(input.commitment.as_str());
|
||||
let commitment = match commitment {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let min_context_slot = parse_min_context_slot(input.min_context_slot.as_deref());
|
||||
let min_context_slot = match min_context_slot {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let scope = request_scope(&input);
|
||||
let scope = match scope {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let page_size = usize::try_from(input.page_size);
|
||||
let page_size = match page_size {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(request_conversion_error("page_size", error)),
|
||||
};
|
||||
let max_pages = usize::try_from(input.max_pages);
|
||||
let max_pages = match max_pages {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(request_conversion_error("max_pages", error)),
|
||||
};
|
||||
let max_candidates = usize::try_from(input.max_candidates);
|
||||
let max_candidates = match max_candidates {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(request_conversion_error("max_candidates", error)),
|
||||
};
|
||||
let hydration_concurrency = usize::try_from(input.hydration_concurrency);
|
||||
let hydration_concurrency = match hydration_concurrency {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(request_conversion_error("hydration_concurrency", error)),
|
||||
};
|
||||
return ksp_job_backfill_lib::BackfillRequest::new(
|
||||
job_id,
|
||||
network,
|
||||
role,
|
||||
commitment,
|
||||
scope,
|
||||
page_size,
|
||||
max_pages,
|
||||
max_candidates,
|
||||
hydration_concurrency,
|
||||
min_context_slot,
|
||||
);
|
||||
}
|
||||
|
||||
/// Builds a safe request preview from one already validated KSP Backfill request.
|
||||
pub(crate) fn project_backfill_request(request: &ksp_job_backfill_lib::BackfillRequest) -> ksp_core_lib::Result<crate::BackfillRequestPreviewDto> {
|
||||
let explicit_signature_count = match request.scope().signatures() {
|
||||
std::option::Option::Some(signatures) => u32::try_from(signatures.len()),
|
||||
std::option::Option::None => std::result::Result::Ok(0),
|
||||
};
|
||||
let explicit_signature_count = match explicit_signature_count {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(request_conversion_error("explicit_signature_count", error)),
|
||||
};
|
||||
let hydration_concurrency = u32::try_from(request.hydration_concurrency());
|
||||
let hydration_concurrency = match hydration_concurrency {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(request_conversion_error("hydration_concurrency", error)),
|
||||
};
|
||||
let max_candidates = u32::try_from(request.max_candidates());
|
||||
let max_candidates = match max_candidates {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(request_conversion_error("max_candidates", error)),
|
||||
};
|
||||
let max_pages = u32::try_from(request.max_pages());
|
||||
let max_pages = match max_pages {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(request_conversion_error("max_pages", error)),
|
||||
};
|
||||
let page_size = u32::try_from(request.page_size());
|
||||
let page_size = match page_size {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(request_conversion_error("page_size", error)),
|
||||
};
|
||||
return std::result::Result::Ok(crate::BackfillRequestPreviewDto {
|
||||
address_present: request.scope().address().is_some(),
|
||||
anchor_present: request.scope().anchor().is_some(),
|
||||
commitment: request.commitment().code().to_owned(),
|
||||
explicit_signature_count,
|
||||
hydration_concurrency,
|
||||
http_role: request.role().as_str().to_owned(),
|
||||
max_candidates,
|
||||
max_pages,
|
||||
min_context_slot_present: request.min_context_slot().is_some(),
|
||||
network: request.network().as_str().to_owned(),
|
||||
page_size,
|
||||
scope_kind: request.scope().kind().code().to_owned(),
|
||||
});
|
||||
}
|
||||
|
||||
fn parse_min_context_slot(value: std::option::Option<&str>) -> ksp_core_lib::Result<std::option::Option<u64>> {
|
||||
let value = match value {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return std::result::Result::Ok(std::option::Option::None),
|
||||
};
|
||||
if value.is_empty() || value.trim() != value {
|
||||
return std::result::Result::Err(request_field_error("min_context_slot"));
|
||||
}
|
||||
let parsed = value.parse::<u64>();
|
||||
return match parsed {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(std::option::Option::Some(value)),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(request_field_error("min_context_slot").with_source(error)),
|
||||
};
|
||||
}
|
||||
|
||||
fn parse_pubkey(value: &str, field: &'static str) -> ksp_core_lib::Result<ksp_core_lib::Pubkey> {
|
||||
if value.is_empty() || value.trim() != value {
|
||||
return std::result::Result::Err(request_field_error(field));
|
||||
}
|
||||
let parsed = value.parse::<ksp_core_lib::Pubkey>();
|
||||
return match parsed {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(request_field_error(field).with_source(error)),
|
||||
};
|
||||
}
|
||||
|
||||
fn parse_signature(value: &str, field: &'static str) -> ksp_core_lib::Result<ksp_job_backfill_lib::BackfillSignature> {
|
||||
if value.is_empty() || value.trim() != value {
|
||||
return std::result::Result::Err(request_field_error(field));
|
||||
}
|
||||
let parsed = ksp_job_backfill_lib::BackfillSignature::new(value.to_owned());
|
||||
return match parsed {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(request_field_error(field).with_source(error)),
|
||||
};
|
||||
}
|
||||
|
||||
fn request_commitment(value: &str) -> ksp_core_lib::Result<ksp_job_backfill_lib::BackfillCommitment> {
|
||||
return match value {
|
||||
"confirmed" => std::result::Result::Ok(ksp_job_backfill_lib::BackfillCommitment::Confirmed),
|
||||
"finalized" => std::result::Result::Ok(ksp_job_backfill_lib::BackfillCommitment::Finalized),
|
||||
_ => std::result::Result::Err(request_field_error("commitment")),
|
||||
};
|
||||
}
|
||||
|
||||
fn request_conversion_error(field: &'static str, source: impl std::error::Error + Send + Sync + 'static) -> ksp_core_lib::Error {
|
||||
return request_field_error(field).with_source(source);
|
||||
}
|
||||
|
||||
fn request_field_error(field: &'static str) -> ksp_core_lib::Error {
|
||||
return ksp_core_lib::Error::new(crate::ERROR_CODE_BACKFILL_REQUEST_INVALID, "Backfill Desk campaign request is invalid").with_context("field", field);
|
||||
}
|
||||
|
||||
fn request_network(options: &crate::BackfillDeskOptionsDto) -> ksp_core_lib::Result<ksp_store_lib::RawNetworkId> {
|
||||
let network = options.store_network.as_deref();
|
||||
let network = match network {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return std::result::Result::Err(request_field_error("network")),
|
||||
};
|
||||
let parsed = ksp_store_lib::RawNetworkId::new(network.to_owned());
|
||||
return match parsed {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(request_field_error("network").with_source(error)),
|
||||
};
|
||||
}
|
||||
|
||||
fn request_role(value: &str, options: &crate::BackfillDeskOptionsDto) -> ksp_core_lib::Result<ksp_onchain_transport_lib::HttpRoleName> {
|
||||
let admitted = options.http_routes.iter().any(|route| return route.role == value);
|
||||
if !admitted || value.is_empty() || value.trim() != value {
|
||||
return std::result::Result::Err(request_field_error("http_role"));
|
||||
}
|
||||
return std::result::Result::Ok(ksp_onchain_transport_lib::HttpRoleName::new(value.to_owned()));
|
||||
}
|
||||
|
||||
fn request_scope(input: &crate::BackfillStartRequestDto) -> ksp_core_lib::Result<ksp_job_backfill_lib::BackfillScope> {
|
||||
return match input.scope_kind.as_str() {
|
||||
"latest_address" => latest_address_scope(input),
|
||||
"before_address" => before_address_scope(input),
|
||||
"after_address" => after_address_scope(input),
|
||||
"explicit_signatures" => explicit_signatures_scope(input),
|
||||
_ => std::result::Result::Err(request_field_error("scope_kind")),
|
||||
};
|
||||
}
|
||||
|
||||
fn latest_address_scope(input: &crate::BackfillStartRequestDto) -> ksp_core_lib::Result<ksp_job_backfill_lib::BackfillScope> {
|
||||
let shape = require_no_anchor_or_signatures(input);
|
||||
if let std::result::Result::Err(error) = shape {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let address = required_address(input.address.as_deref());
|
||||
let address = match address {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return std::result::Result::Ok(ksp_job_backfill_lib::BackfillScope::latest_address(address));
|
||||
}
|
||||
|
||||
fn before_address_scope(input: &crate::BackfillStartRequestDto) -> ksp_core_lib::Result<ksp_job_backfill_lib::BackfillScope> {
|
||||
let shape = require_no_explicit_signatures(input);
|
||||
if let std::result::Result::Err(error) = shape {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let address = required_address(input.address.as_deref());
|
||||
let address = match address {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let anchor = required_anchor(input.anchor_signature.as_deref());
|
||||
let anchor = match anchor {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return std::result::Result::Ok(ksp_job_backfill_lib::BackfillScope::before_address(address, anchor));
|
||||
}
|
||||
|
||||
fn after_address_scope(input: &crate::BackfillStartRequestDto) -> ksp_core_lib::Result<ksp_job_backfill_lib::BackfillScope> {
|
||||
let shape = require_no_explicit_signatures(input);
|
||||
if let std::result::Result::Err(error) = shape {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let address = required_address(input.address.as_deref());
|
||||
let address = match address {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let anchor = required_anchor(input.anchor_signature.as_deref());
|
||||
let anchor = match anchor {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return std::result::Result::Ok(ksp_job_backfill_lib::BackfillScope::after_address(address, anchor));
|
||||
}
|
||||
|
||||
fn explicit_signatures_scope(input: &crate::BackfillStartRequestDto) -> ksp_core_lib::Result<ksp_job_backfill_lib::BackfillScope> {
|
||||
if input.address.is_some() || input.anchor_signature.is_some() || input.min_context_slot.is_some() {
|
||||
return std::result::Result::Err(request_field_error("scope"));
|
||||
}
|
||||
if input.explicit_signatures.is_empty() || input.explicit_signatures.len() > ksp_job_backfill_lib::MAX_BACKFILL_CANDIDATES {
|
||||
return std::result::Result::Err(request_field_error("explicit_signatures"));
|
||||
}
|
||||
let mut signatures = std::vec::Vec::with_capacity(input.explicit_signatures.len());
|
||||
for value in &input.explicit_signatures {
|
||||
let signature = parse_signature(value.as_str(), "explicit_signatures");
|
||||
let signature = match signature {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
signatures.push(signature);
|
||||
}
|
||||
return ksp_job_backfill_lib::BackfillScope::explicit_signatures(signatures);
|
||||
}
|
||||
|
||||
fn require_no_anchor_or_signatures(input: &crate::BackfillStartRequestDto) -> ksp_core_lib::Result<()> {
|
||||
if input.anchor_signature.is_some() || !input.explicit_signatures.is_empty() {
|
||||
return std::result::Result::Err(request_field_error("scope"));
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn require_no_explicit_signatures(input: &crate::BackfillStartRequestDto) -> ksp_core_lib::Result<()> {
|
||||
if !input.explicit_signatures.is_empty() {
|
||||
return std::result::Result::Err(request_field_error("explicit_signatures"));
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn required_address(value: std::option::Option<&str>) -> ksp_core_lib::Result<ksp_core_lib::Pubkey> {
|
||||
return match value {
|
||||
std::option::Option::Some(value) => parse_pubkey(value, "address"),
|
||||
std::option::Option::None => std::result::Result::Err(request_field_error("address")),
|
||||
};
|
||||
}
|
||||
|
||||
fn required_anchor(value: std::option::Option<&str>) -> ksp_core_lib::Result<ksp_job_backfill_lib::BackfillSignature> {
|
||||
return match value {
|
||||
std::option::Option::Some(value) => parse_signature(value, "anchor_signature"),
|
||||
std::option::Option::None => std::result::Result::Err(request_field_error("anchor_signature")),
|
||||
};
|
||||
}
|
||||
|
||||
fn validate_composition(options: &crate::BackfillDeskOptionsDto) -> ksp_core_lib::Result<()> {
|
||||
if options.composition_ready && options.transport_ready && options.store_ready && options.network_coherent {
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_BACKFILL_COMPOSITION_NOT_READY,
|
||||
"Backfill Desk cannot admit a campaign before Transport and Store composition is ready",
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/backfill_request.rs"]
|
||||
mod tests;
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/src/constants.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
//! Application-owned tracing targets and domains.
|
||||
|
||||
@@ -9,10 +9,20 @@ pub(crate) const COMPOSITE_COMPONENT_ID_LOGGING: &str = "logging";
|
||||
pub(crate) const COMPOSITE_COMPONENT_ID_STORE: &str = "store";
|
||||
/// Composite component identifier for Transport.
|
||||
pub(crate) const COMPOSITE_COMPONENT_ID_TRANSPORT: &str = "transport";
|
||||
/// Initial hydration concurrency proposed by the Desk for a new campaign.
|
||||
pub(crate) const DEFAULT_BACKFILL_HYDRATION_CONCURRENCY: u32 = 4;
|
||||
/// Initial candidate cap proposed by the Desk for a new campaign.
|
||||
pub(crate) const DEFAULT_BACKFILL_MAX_CANDIDATES: u32 = 1_000;
|
||||
/// Initial discovery-page cap proposed by the Desk for a new campaign.
|
||||
pub(crate) const DEFAULT_BACKFILL_MAX_PAGES: u32 = 10;
|
||||
/// Initial signature page size proposed by the Desk for a new campaign.
|
||||
pub(crate) const DEFAULT_BACKFILL_PAGE_SIZE: u32 = 100;
|
||||
/// Structured domain used while bootstrapping Config and Logging.
|
||||
pub(crate) const TRACING_DOMAIN_BOOTSTRAP: &str = "backfill.bootstrap";
|
||||
/// Structured domain used by technical frontend events.
|
||||
pub(crate) const TRACING_DOMAIN_FRONTEND: &str = "frontend";
|
||||
/// Structured domain used by Backfill campaign admission and request mapping.
|
||||
pub(crate) const TRACING_DOMAIN_REQUEST: &str = "backfill.request";
|
||||
/// Structured domain used by the Backfill Desk shell.
|
||||
pub(crate) const TRACING_DOMAIN_SHELL: &str = "backfill.shell";
|
||||
/// Structured domain used by Store readiness and shutdown operations.
|
||||
|
||||
196
crates/ksp-app-backfill-desk/src/dto_backfill.rs
Normal file
196
crates/ksp-app-backfill-desk/src/dto_backfill.rs
Normal file
@@ -0,0 +1,196 @@
|
||||
// file: crates/ksp-app-backfill-desk/src/dto_backfill.rs
|
||||
// version: 1
|
||||
|
||||
//! Application-owned Backfill campaign DTOs and backend-derived request limits.
|
||||
|
||||
use ts_rs::TS; // rust-rules: trait-import
|
||||
|
||||
/// Backend-derived limits and application defaults rendered by the Backfill campaign form.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_backfill_desk/dto_backfill/BackfillRequestLimitsDto.ts")]
|
||||
pub(crate) struct BackfillRequestLimitsDto {
|
||||
/// Initial hydration concurrency proposed by this Desk while remaining below the Job-owned maximum.
|
||||
pub(crate) default_hydration_concurrency: u32,
|
||||
/// Initial candidate cap proposed by this Desk while remaining below the Job-owned maximum.
|
||||
pub(crate) default_max_candidates: u32,
|
||||
/// Initial discovery-page cap proposed by this Desk while remaining below the Job-owned maximum.
|
||||
pub(crate) default_max_pages: u32,
|
||||
/// Initial signature page size proposed by this Desk while remaining below the Job-owned maximum.
|
||||
pub(crate) default_page_size: u32,
|
||||
/// Maximum hydration concurrency owned by `ksp-job-backfill-lib`.
|
||||
pub(crate) max_hydration_concurrency: u32,
|
||||
/// Maximum candidate count owned by `ksp-job-backfill-lib`.
|
||||
pub(crate) max_candidates: u32,
|
||||
/// Maximum discovery-page count owned by `ksp-job-backfill-lib`.
|
||||
pub(crate) max_pages: u32,
|
||||
/// Maximum signature page size owned by `ksp-job-backfill-lib`.
|
||||
pub(crate) max_page_size: u32,
|
||||
/// Maximum encoded transaction-signature text length owned by `ksp-job-backfill-lib`.
|
||||
pub(crate) max_signature_text_bytes: u32,
|
||||
/// Minimum encoded transaction-signature text length owned by `ksp-job-backfill-lib`.
|
||||
pub(crate) min_signature_text_bytes: u32,
|
||||
}
|
||||
|
||||
/// App-owned request received from the Backfill campaign form before a Job is started.
|
||||
///
|
||||
/// Network and physical endpoint information are intentionally absent. The backend derives the
|
||||
/// network from the configured Store and maps `http_role` only after checking the current safe
|
||||
/// Transport inventory.
|
||||
#[derive(serde::Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_backfill_desk/dto_backfill/BackfillStartRequestDto.ts")]
|
||||
pub(crate) struct BackfillStartRequestDto {
|
||||
/// Optional address text used only by address-based scopes.
|
||||
pub(crate) address: std::option::Option<String>,
|
||||
/// Optional exclusive anchor signature used only by Before/After scopes.
|
||||
pub(crate) anchor_signature: std::option::Option<String>,
|
||||
/// Stable Backfill commitment code.
|
||||
pub(crate) commitment: String,
|
||||
/// Explicit signature texts used only by the explicit-signatures scope.
|
||||
pub(crate) explicit_signatures: std::vec::Vec<String>,
|
||||
/// Maximum number of concurrent candidate hydrations.
|
||||
pub(crate) hydration_concurrency: u32,
|
||||
/// Logical HTTP role selected from `BackfillDeskOptionsDto::http_routes`.
|
||||
pub(crate) http_role: String,
|
||||
/// Maximum number of candidates admitted by the campaign.
|
||||
pub(crate) max_candidates: u32,
|
||||
/// Maximum number of address-discovery pages admitted by the campaign.
|
||||
pub(crate) max_pages: u32,
|
||||
/// Optional minimum context slot encoded as decimal text to avoid JavaScript integer precision loss.
|
||||
pub(crate) min_context_slot: std::option::Option<String>,
|
||||
/// Signature page size used by address discovery.
|
||||
pub(crate) page_size: u32,
|
||||
/// Stable Backfill scope kind code.
|
||||
pub(crate) scope_kind: String,
|
||||
}
|
||||
|
||||
/// Safe projection proving that one app request mapped to the KSP Backfill contract.
|
||||
///
|
||||
/// Address and signature values are intentionally reduced to presence/count metadata. This DTO is
|
||||
/// suitable for request validation feedback before the runtime Start slice exists.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_backfill_desk/dto_backfill/BackfillRequestPreviewDto.ts")]
|
||||
pub(crate) struct BackfillRequestPreviewDto {
|
||||
/// Whether an address participates in the validated scope.
|
||||
pub(crate) address_present: bool,
|
||||
/// Whether an exclusive anchor participates in the validated scope.
|
||||
pub(crate) anchor_present: bool,
|
||||
/// Stable validated commitment code.
|
||||
pub(crate) commitment: String,
|
||||
/// Number of explicit signatures after Backfill-owned stable deduplication.
|
||||
pub(crate) explicit_signature_count: u32,
|
||||
/// Validated hydration concurrency.
|
||||
pub(crate) hydration_concurrency: u32,
|
||||
/// Validated logical HTTP role.
|
||||
pub(crate) http_role: String,
|
||||
/// Validated maximum candidate count.
|
||||
pub(crate) max_candidates: u32,
|
||||
/// Validated maximum discovery-page count.
|
||||
pub(crate) max_pages: u32,
|
||||
/// Whether a minimum context slot participates in the validated request.
|
||||
pub(crate) min_context_slot_present: bool,
|
||||
/// Backend-derived logical Store network.
|
||||
pub(crate) network: String,
|
||||
/// Validated signature page size.
|
||||
pub(crate) page_size: u32,
|
||||
/// Stable validated scope kind code.
|
||||
pub(crate) scope_kind: String,
|
||||
}
|
||||
|
||||
/// Returns the exact commitment codes currently admitted by the Backfill runtime.
|
||||
#[must_use]
|
||||
pub(crate) fn backfill_commitment_codes() -> std::vec::Vec<String> {
|
||||
return vec![ksp_job_backfill_lib::BackfillCommitment::Finalized.code().to_owned(), ksp_job_backfill_lib::BackfillCommitment::Confirmed.code().to_owned()];
|
||||
}
|
||||
|
||||
/// Returns the exact HTTP scope codes currently admitted by the Backfill runtime.
|
||||
#[must_use]
|
||||
pub(crate) fn backfill_scope_kind_codes() -> std::vec::Vec<String> {
|
||||
return vec![
|
||||
ksp_job_backfill_lib::BackfillScopeKind::LatestAddress.code().to_owned(),
|
||||
ksp_job_backfill_lib::BackfillScopeKind::BeforeAddress.code().to_owned(),
|
||||
ksp_job_backfill_lib::BackfillScopeKind::AfterAddress.code().to_owned(),
|
||||
ksp_job_backfill_lib::BackfillScopeKind::ExplicitSignatures.code().to_owned(),
|
||||
];
|
||||
}
|
||||
|
||||
/// Builds frontend-safe request limits directly from the public Backfill constants.
|
||||
pub(crate) fn backfill_request_limits() -> ksp_core_lib::Result<BackfillRequestLimitsDto> {
|
||||
let max_hydration_concurrency = usize_to_u32(ksp_job_backfill_lib::MAX_BACKFILL_HYDRATION_CONCURRENCY, "max_hydration_concurrency");
|
||||
let max_hydration_concurrency = match max_hydration_concurrency {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let max_candidates = usize_to_u32(ksp_job_backfill_lib::MAX_BACKFILL_CANDIDATES, "max_candidates");
|
||||
let max_candidates = match max_candidates {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let max_pages = usize_to_u32(ksp_job_backfill_lib::MAX_BACKFILL_PAGES, "max_pages");
|
||||
let max_pages = match max_pages {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let max_page_size = usize_to_u32(ksp_job_backfill_lib::MAX_BACKFILL_PAGE_SIZE, "max_page_size");
|
||||
let max_page_size = match max_page_size {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let max_signature_text_bytes = usize_to_u32(ksp_job_backfill_lib::MAX_BACKFILL_SIGNATURE_TEXT_BYTES, "max_signature_text_bytes");
|
||||
let max_signature_text_bytes = match max_signature_text_bytes {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let min_signature_text_bytes = usize_to_u32(ksp_job_backfill_lib::MIN_BACKFILL_SIGNATURE_TEXT_BYTES, "min_signature_text_bytes");
|
||||
let min_signature_text_bytes = match min_signature_text_bytes {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if crate::DEFAULT_BACKFILL_HYDRATION_CONCURRENCY == 0 || crate::DEFAULT_BACKFILL_HYDRATION_CONCURRENCY > max_hydration_concurrency {
|
||||
return std::result::Result::Err(limit_contract_error("default_hydration_concurrency"));
|
||||
}
|
||||
if crate::DEFAULT_BACKFILL_MAX_CANDIDATES == 0 || crate::DEFAULT_BACKFILL_MAX_CANDIDATES > max_candidates {
|
||||
return std::result::Result::Err(limit_contract_error("default_max_candidates"));
|
||||
}
|
||||
if crate::DEFAULT_BACKFILL_MAX_PAGES == 0 || crate::DEFAULT_BACKFILL_MAX_PAGES > max_pages {
|
||||
return std::result::Result::Err(limit_contract_error("default_max_pages"));
|
||||
}
|
||||
if crate::DEFAULT_BACKFILL_PAGE_SIZE == 0 || crate::DEFAULT_BACKFILL_PAGE_SIZE > max_page_size {
|
||||
return std::result::Result::Err(limit_contract_error("default_page_size"));
|
||||
}
|
||||
return std::result::Result::Ok(BackfillRequestLimitsDto {
|
||||
default_hydration_concurrency: crate::DEFAULT_BACKFILL_HYDRATION_CONCURRENCY,
|
||||
default_max_candidates: crate::DEFAULT_BACKFILL_MAX_CANDIDATES,
|
||||
default_max_pages: crate::DEFAULT_BACKFILL_MAX_PAGES,
|
||||
default_page_size: crate::DEFAULT_BACKFILL_PAGE_SIZE,
|
||||
max_hydration_concurrency,
|
||||
max_candidates,
|
||||
max_pages,
|
||||
max_page_size,
|
||||
max_signature_text_bytes,
|
||||
min_signature_text_bytes,
|
||||
});
|
||||
}
|
||||
|
||||
fn limit_contract_error(field: &'static str) -> ksp_core_lib::Error {
|
||||
return ksp_core_lib::Error::new(crate::ERROR_CODE_APP_STATE_INVALID, "Backfill Desk request-limit projection is internally inconsistent")
|
||||
.with_context("field", field);
|
||||
}
|
||||
|
||||
fn usize_to_u32(value: usize, field: &'static str) -> ksp_core_lib::Result<u32> {
|
||||
let converted = u32::try_from(value);
|
||||
return match converted {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_APP_STATE_INVALID, "Backfill Desk cannot project a Job-owned bound to the frontend")
|
||||
.with_context("field", field)
|
||||
.with_source(error),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/dto_backfill.rs"]
|
||||
mod tests;
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/src/dto_common.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
//! Common Tauri DTOs shared by the Backfill Desk shell.
|
||||
|
||||
@@ -21,15 +21,19 @@ pub(crate) struct BackfillHttpRouteOptionDto {
|
||||
pub(crate) role: String,
|
||||
}
|
||||
|
||||
/// Safe Transport-and-Store readiness subset of the Backfill Desk options contract.
|
||||
/// Safe Backfill Desk options contract combining readiness, HTTP routes and Job-owned campaign bounds.
|
||||
///
|
||||
/// Campaign scopes, commitments and backend-owned bounds are added in the dedicated request/DTO slice. Endpoint URLs and credentials are intentionally absent.
|
||||
/// Endpoint URLs, credentials and physical routing details are intentionally absent.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_backfill_desk/dto_common/BackfillDeskOptionsDto.ts")]
|
||||
pub(crate) struct BackfillDeskOptionsDto {
|
||||
/// Stable commitment codes currently admitted by `ksp-job-backfill-lib`.
|
||||
pub(crate) commitments: std::vec::Vec<String>,
|
||||
/// Selectable logical HTTP routes that support both RPC methods required by the Backfill runtime.
|
||||
pub(crate) http_routes: std::vec::Vec<BackfillHttpRouteOptionDto>,
|
||||
/// Backend-owned maxima plus application defaults used by the campaign form.
|
||||
pub(crate) limits: crate::BackfillRequestLimitsDto,
|
||||
/// Whether the Transport and Store readiness gates jointly permit later Backfill composition.
|
||||
pub(crate) composition_ready: bool,
|
||||
/// Distinct enabled HTTP cluster labels selected by the active Transport profile.
|
||||
@@ -44,6 +48,8 @@ pub(crate) struct BackfillDeskOptionsDto {
|
||||
pub(crate) store_ready: bool,
|
||||
/// Safe startup diagnostic when Transport configuration could not be resolved or constructed.
|
||||
pub(crate) transport_diagnostic: std::option::Option<CommandErrorDto>,
|
||||
/// Stable HTTP scope codes currently admitted by `ksp-job-backfill-lib`.
|
||||
pub(crate) scope_kinds: std::vec::Vec<String>,
|
||||
/// Whether Transport currently has one coherent network and at least one compatible available role.
|
||||
pub(crate) transport_ready: bool,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/src/errors.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
//! Application-local error codes for Backfill Desk composition and desktop runtime surfaces.
|
||||
|
||||
@@ -7,6 +7,11 @@
|
||||
pub(crate) const ERROR_CODE_APP_STATE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("backfill_desk", "app_state_invalid");
|
||||
/// Shared Backfill Desk runtime state cannot be locked safely.
|
||||
pub(crate) const ERROR_CODE_APP_STATE_LOCK_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("backfill_desk", "app_state_lock_failed");
|
||||
/// Backfill Desk cannot admit a campaign before Transport and Store composition is ready.
|
||||
pub(crate) const ERROR_CODE_BACKFILL_COMPOSITION_NOT_READY: ksp_core_lib::ErrorCode =
|
||||
ksp_core_lib::ErrorCode::new("backfill_desk", "backfill_composition_not_ready");
|
||||
/// Backfill Desk received a campaign field or logical route that cannot map to the Backfill contract.
|
||||
pub(crate) const ERROR_CODE_BACKFILL_REQUEST_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("backfill_desk", "backfill_request_invalid");
|
||||
/// Backfill Desk composite configuration is missing or references an unexpected document.
|
||||
pub(crate) const ERROR_CODE_CONFIG_COMPOSITE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("backfill_desk", "config_composite_invalid");
|
||||
/// Frontend logging requested an unsupported level.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/src/lib.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
//! Tauri desktop application scaffold for controlling and inspecting KSP RAW backfill jobs.
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
#![warn(missing_docs)]
|
||||
|
||||
mod app_state;
|
||||
mod backfill_request;
|
||||
mod bootstrap;
|
||||
mod constants;
|
||||
mod dto_backfill;
|
||||
mod dto_common;
|
||||
mod errors;
|
||||
mod frontend_logging;
|
||||
@@ -26,6 +28,10 @@ pub use self::tauri::run;
|
||||
|
||||
/// Shared Backfill Desk application state managed by Tauri.
|
||||
pub(crate) use self::app_state::AppState;
|
||||
/// Maps one app-owned campaign DTO to the validated KSP Backfill request contract.
|
||||
pub(crate) use self::backfill_request::map_backfill_request;
|
||||
/// Projects one validated Backfill request without returning address or signature values.
|
||||
pub(crate) use self::backfill_request::project_backfill_request;
|
||||
/// Crate-internal Logging startup state shared by the application state.
|
||||
pub(crate) use self::bootstrap::LoggingStartup;
|
||||
/// Builds the Config management facade from the common KSP CLI bootstrap contract.
|
||||
@@ -42,10 +48,20 @@ pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_LOGGING;
|
||||
pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_STORE;
|
||||
/// Composite component identifier for Transport.
|
||||
pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_TRANSPORT;
|
||||
/// Initial hydration concurrency proposed by the Backfill campaign form.
|
||||
pub(crate) use self::constants::DEFAULT_BACKFILL_HYDRATION_CONCURRENCY;
|
||||
/// Initial candidate cap proposed by the Backfill campaign form.
|
||||
pub(crate) use self::constants::DEFAULT_BACKFILL_MAX_CANDIDATES;
|
||||
/// Initial discovery-page cap proposed by the Backfill campaign form.
|
||||
pub(crate) use self::constants::DEFAULT_BACKFILL_MAX_PAGES;
|
||||
/// Initial signature page size proposed by the Backfill campaign form.
|
||||
pub(crate) use self::constants::DEFAULT_BACKFILL_PAGE_SIZE;
|
||||
/// Structured domain used while bootstrapping Config and Logging.
|
||||
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 by Backfill campaign admission and request mapping.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_REQUEST;
|
||||
/// Structured domain used by the Backfill Desk shell.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_SHELL;
|
||||
/// Structured domain used by Store readiness and shutdown operations.
|
||||
@@ -62,7 +78,19 @@ pub(crate) use self::constants::TRACING_TARGET_FRONTEND;
|
||||
pub(crate) use self::constants::TRACING_TARGET_FRONTEND_MAIN;
|
||||
/// Owning target for splash-window frontend events.
|
||||
pub(crate) use self::constants::TRACING_TARGET_FRONTEND_SPLASH;
|
||||
/// Safe Transport-and-Store readiness subset of the Backfill Desk options contract.
|
||||
/// Backend-derived request bounds and Desk defaults for the campaign form.
|
||||
pub(crate) use self::dto_backfill::BackfillRequestLimitsDto;
|
||||
/// Safe request preview produced after strict backend mapping.
|
||||
pub(crate) use self::dto_backfill::BackfillRequestPreviewDto;
|
||||
/// App-owned campaign request received from the frontend.
|
||||
pub(crate) use self::dto_backfill::BackfillStartRequestDto;
|
||||
/// Returns commitment codes admitted by the current Backfill contract.
|
||||
pub(crate) use self::dto_backfill::backfill_commitment_codes;
|
||||
/// Builds frontend-safe campaign limits from Job-owned public constants.
|
||||
pub(crate) use self::dto_backfill::backfill_request_limits;
|
||||
/// Returns scope kind codes admitted by the current Backfill contract.
|
||||
pub(crate) use self::dto_backfill::backfill_scope_kind_codes;
|
||||
/// Safe Backfill Desk options contract combining readiness and campaign metadata.
|
||||
pub(crate) use self::dto_common::BackfillDeskOptionsDto;
|
||||
/// Safe logical HTTP route exposed for operator selection.
|
||||
pub(crate) use self::dto_common::BackfillHttpRouteOptionDto;
|
||||
@@ -74,6 +102,10 @@ pub(crate) use self::dto_common::ShellStatusDto;
|
||||
pub(crate) use self::errors::ERROR_CODE_APP_STATE_INVALID;
|
||||
/// Shared Backfill Desk runtime state cannot be locked safely.
|
||||
pub(crate) use self::errors::ERROR_CODE_APP_STATE_LOCK_FAILED;
|
||||
/// Backfill Desk cannot admit a campaign before Transport and Store composition is ready.
|
||||
pub(crate) use self::errors::ERROR_CODE_BACKFILL_COMPOSITION_NOT_READY;
|
||||
/// Backfill Desk received a campaign field that cannot map to the Backfill contract.
|
||||
pub(crate) use self::errors::ERROR_CODE_BACKFILL_REQUEST_INVALID;
|
||||
/// Backfill Desk composite configuration is missing or references an unexpected document.
|
||||
pub(crate) use self::errors::ERROR_CODE_CONFIG_COMPOSITE_INVALID;
|
||||
/// Frontend logging requested an unsupported level.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/src/tauri.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
//! Tauri runtime assembly for the KSP Backfill desktop application.
|
||||
|
||||
@@ -75,7 +75,13 @@ fn configure_plugins(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<taur
|
||||
|
||||
#[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![backfill_options, emit_frontend_log, get_runtime_status, splash_frontend_ready]);
|
||||
return builder.invoke_handler(tauri::generate_handler![
|
||||
backfill_options,
|
||||
backfill_validate_request,
|
||||
emit_frontend_log,
|
||||
get_runtime_status,
|
||||
splash_frontend_ready
|
||||
]);
|
||||
}
|
||||
|
||||
fn configure_setup(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
|
||||
@@ -158,6 +164,18 @@ fn backfill_options(state: tauri::State<'_, crate::AppState>) -> std::result::Re
|
||||
};
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn backfill_validate_request(
|
||||
request: crate::BackfillStartRequestDto,
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<crate::BackfillRequestPreviewDto, crate::CommandErrorDto> {
|
||||
let result = state.validate_backfill_request(request);
|
||||
return match result {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(project_command_error("backfill_validate_request", crate::TRACING_DOMAIN_REQUEST, &error)),
|
||||
};
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn emit_frontend_log(payload: crate::FrontendLogPayloadDto) -> std::result::Result<(), crate::CommandErrorDto> {
|
||||
let result = crate::emit_frontend_log_event(payload);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/src/transport_runtime.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! Composite-selected HTTP Transport readiness and route inventory owned by Backfill Desk.
|
||||
|
||||
@@ -35,12 +35,20 @@ impl TransportRuntime {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let limits = crate::backfill_request_limits();
|
||||
let limits = match limits {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let transport_ready = configured_networks.len() == 1 && !http_routes.is_empty() && snapshot.available_endpoint_count() > 0;
|
||||
return std::result::Result::Ok(crate::BackfillDeskOptionsDto {
|
||||
commitments: crate::backfill_commitment_codes(),
|
||||
composition_ready: false,
|
||||
configured_networks,
|
||||
http_routes,
|
||||
limits,
|
||||
network_coherent: false,
|
||||
scope_kinds: crate::backfill_scope_kind_codes(),
|
||||
store_diagnostic: std::option::Option::None,
|
||||
store_network: std::option::Option::None,
|
||||
store_ready: false,
|
||||
|
||||
Reference in New Issue
Block a user