v0.3.7-pre.012
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
# file: Cargo.toml
|
||||
# version: 435
|
||||
# version: 436
|
||||
|
||||
[workspace]
|
||||
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"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.3.7-pre.11"
|
||||
version = "0.3.7-pre.12"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-app-backfill-desk/frontend/main.html -->
|
||||
<!-- version: 7 -->
|
||||
<!-- version: 8 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -87,7 +87,9 @@
|
||||
</div>
|
||||
<div id="backfillAddressGroup" class="col-12">
|
||||
<label for="backfillAddress" class="form-label">Adresse Solana</label>
|
||||
<input id="backfillAddress" class="form-control font-monospace" type="text" autocomplete="off" spellcheck="false">
|
||||
<input id="backfillAddress" class="form-control font-monospace" type="text" list="backfillProgramIds" autocomplete="off" spellcheck="false" aria-describedby="backfillProgramIdAutocompleteHelp">
|
||||
<datalist id="backfillProgramIds"></datalist>
|
||||
<div id="backfillProgramIdAutocompleteHelp" class="form-text">Suggestions issues du registre canonique KSP ; toute adresse Solana valide reste acceptée.</div>
|
||||
</div>
|
||||
<div id="backfillAnchorGroup" class="col-12" hidden>
|
||||
<label for="backfillAnchorSignature" class="form-label">Signature d'ancrage exclusive</label>
|
||||
@@ -149,6 +151,7 @@
|
||||
<div class="card-body">
|
||||
<div id="backfillCancelFeedback" class="alert alert-secondary py-2" role="status" aria-live="polite" hidden></div>
|
||||
<div id="backfillResumeFeedback" class="alert alert-secondary py-2" role="status" aria-live="polite" hidden></div>
|
||||
<div id="backfillTerminalSummary" class="alert alert-secondary py-2" role="status" aria-live="polite" hidden></div>
|
||||
<dl class="row mb-0 app-runtime-list">
|
||||
<dt class="col-sm-5">Job</dt><dd id="runStatusJobId" class="col-sm-7 font-monospace">—</dd>
|
||||
<dt class="col-sm-5">Lifecycle</dt><dd id="runStatusLifecycle" class="col-sm-7">—</dd>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/frontend/ts/main.ts
|
||||
// version: 7
|
||||
// version: 8
|
||||
|
||||
import "bootstrap";
|
||||
import ResizeObserver from "resize-observer-polyfill";
|
||||
@@ -14,6 +14,7 @@ import type { BackfillStartRequestDto } from "./bindings/ksp_app_backfill_desk/d
|
||||
import type { BackfillStartResponseDto } from "./bindings/ksp_app_backfill_desk/dto_backfill/BackfillStartResponseDto.ts";
|
||||
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 { ProgramIdAutocompleteOptionDto } from "./bindings/ksp_app_backfill_desk/dto_common/ProgramIdAutocompleteOptionDto.ts";
|
||||
import type { ShellStatusDto } from "./bindings/ksp_app_backfill_desk/dto_common/ShellStatusDto.ts";
|
||||
import { frontendDebug, frontendInfo, frontendTrace, frontendWarn, installFrontendConsoleBridge } from "./frontend_log";
|
||||
import { invokeKsp } from "./invoke";
|
||||
@@ -141,7 +142,56 @@ function refreshStartButton(): void {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function renderProgramIdAutocomplete(options: ProgramIdAutocompleteOptionDto[]): void {
|
||||
const datalist = document.querySelector<HTMLDataListElement>("#backfillProgramIds");
|
||||
if (!datalist) {
|
||||
return;
|
||||
}
|
||||
datalist.replaceChildren();
|
||||
for (const program of options) {
|
||||
const option = document.createElement("option");
|
||||
option.value = program.programId;
|
||||
option.label = `${program.name} — ${program.code}`;
|
||||
option.dataset.code = program.code;
|
||||
option.dataset.domain = program.domain;
|
||||
option.dataset.family = program.family;
|
||||
option.dataset.name = program.name;
|
||||
option.dataset.protocol = program.protocol;
|
||||
datalist.append(option);
|
||||
}
|
||||
const help = document.querySelector<HTMLElement>("#backfillProgramIdAutocompleteHelp");
|
||||
if (help) {
|
||||
help.textContent = `${options.length} Program ID KSP disponibles comme suggestions ; la saisie reste libre.`;
|
||||
}
|
||||
frontendTrace("main", "Backfill Desk Program ID autocomplete dataset rendered", { optionCount: options.length });
|
||||
}
|
||||
|
||||
function selectedProgramAutocompleteEntry(address: string): HTMLOptionElement | null {
|
||||
const datalist = document.querySelector<HTMLDataListElement>("#backfillProgramIds");
|
||||
if (!datalist) {
|
||||
return null;
|
||||
}
|
||||
for (const option of Array.from(datalist.options)) {
|
||||
if (option.value === address) {
|
||||
return option;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function safeCommandErrorText(caughtError: unknown, fallback: string): string {
|
||||
if (typeof caughtError === "object" && caughtError !== null) {
|
||||
const candidate = caughtError as { code?: unknown; domain?: unknown; message?: unknown };
|
||||
if (typeof candidate.domain === "string" && typeof candidate.code === "string" && typeof candidate.message === "string") {
|
||||
return `${candidate.domain}/${candidate.code}: ${candidate.message}`;
|
||||
}
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function renderCampaignContract(options: BackfillDeskOptionsDto): void {
|
||||
renderProgramIdAutocomplete(options.programIdOptions);
|
||||
populateCodeSelect("backfillScopeKind", options.scopeKinds, scopeLabels);
|
||||
populateCodeSelect("backfillCommitment", options.commitments, commitmentLabels);
|
||||
applyNumberInputContract("backfillPageSize", options.limits.maxPageSize, options.limits.defaultPageSize);
|
||||
@@ -296,13 +346,7 @@ function renderRequestValidationError(caughtError: unknown): void {
|
||||
if (!status) {
|
||||
return;
|
||||
}
|
||||
let message = "Requête refusée par le backend.";
|
||||
if (typeof caughtError === "object" && caughtError !== null) {
|
||||
const candidate = caughtError as { code?: unknown; domain?: unknown; message?: unknown };
|
||||
if (typeof candidate.domain === "string" && typeof candidate.code === "string" && typeof candidate.message === "string") {
|
||||
message = `${candidate.domain}/${candidate.code}: ${candidate.message}`;
|
||||
}
|
||||
}
|
||||
const message = safeCommandErrorText(caughtError, "Requête refusée par le backend.");
|
||||
status.hidden = false;
|
||||
status.classList.remove("alert-secondary", "alert-success");
|
||||
status.classList.add("alert-danger");
|
||||
@@ -373,6 +417,16 @@ function bindCampaignForm(): void {
|
||||
void validateBackfillRequest();
|
||||
});
|
||||
}
|
||||
const address = document.querySelector<HTMLInputElement>("#backfillAddress");
|
||||
if (address) {
|
||||
address.addEventListener("change", () => {
|
||||
const selected = selectedProgramAutocompleteEntry(address.value);
|
||||
frontendTrace("main", "Backfill Desk address autocomplete selection evaluated", {
|
||||
programCode: selected?.dataset.code ?? null,
|
||||
registryMatch: selected !== null,
|
||||
});
|
||||
});
|
||||
}
|
||||
const startButton = document.querySelector<HTMLButtonElement>("#startBackfillRequest");
|
||||
if (startButton) {
|
||||
startButton.addEventListener("click", () => {
|
||||
@@ -472,6 +526,11 @@ function renderBackfillRunStatus(status: BackfillRunStatusDto | null, source: "c
|
||||
if (card) {
|
||||
card.hidden = true;
|
||||
}
|
||||
const terminalSummary = document.querySelector<HTMLElement>("#backfillTerminalSummary");
|
||||
if (terminalSummary) {
|
||||
terminalSummary.hidden = true;
|
||||
terminalSummary.textContent = "";
|
||||
}
|
||||
frontendTrace("main", "Backfill Desk latest-value status cleared", { source });
|
||||
return;
|
||||
}
|
||||
@@ -489,6 +548,27 @@ function renderBackfillRunStatus(status: BackfillRunStatusDto | null, source: "c
|
||||
if (card) {
|
||||
card.hidden = false;
|
||||
}
|
||||
const terminalSummary = document.querySelector<HTMLElement>("#backfillTerminalSummary");
|
||||
if (terminalSummary) {
|
||||
terminalSummary.hidden = !status.terminal;
|
||||
terminalSummary.classList.remove("alert-success", "alert-warning", "alert-danger", "alert-secondary");
|
||||
if (status.terminal) {
|
||||
const failure = status.failureDomain === null || status.failureCode === null ? null : `${status.failureDomain}/${status.failureCode}`;
|
||||
if (status.state === "completed") {
|
||||
terminalSummary.classList.add("alert-success");
|
||||
terminalSummary.textContent = `Campagne terminée — ${status.contiguousCompleted} candidats contigus durablement traités.`;
|
||||
} else if (status.state === "cancelled") {
|
||||
terminalSummary.classList.add("alert-warning");
|
||||
terminalSummary.textContent = `Campagne annulée coopérativement — checkpoint ${status.checkpointPresent ? "disponible" : "absent"}.`;
|
||||
} else {
|
||||
terminalSummary.classList.add("alert-danger");
|
||||
terminalSummary.textContent = `Campagne terminée en échec${failure === null ? "" : ` — ${failure}`}.`;
|
||||
}
|
||||
} else {
|
||||
terminalSummary.classList.add("alert-secondary");
|
||||
terminalSummary.textContent = "";
|
||||
}
|
||||
}
|
||||
const values: Record<string, string> = {
|
||||
runStatusJobId: status.jobId,
|
||||
runStatusLifecycle: status.completion === null ? status.state : `${status.state} / ${status.completion}`,
|
||||
@@ -552,11 +632,18 @@ async function resumeBackfillRun(): Promise<void> {
|
||||
state: response.state,
|
||||
});
|
||||
await syncBackfillStatus("resume");
|
||||
} catch (_caughtError) {
|
||||
} catch (caughtError) {
|
||||
resumeInFlight = false;
|
||||
if (resumeButton && currentRunStatus?.terminal && currentRunStatus.checkpointPresent) {
|
||||
resumeButton.disabled = false;
|
||||
}
|
||||
const feedback = document.querySelector<HTMLElement>("#backfillResumeFeedback");
|
||||
if (feedback) {
|
||||
feedback.hidden = false;
|
||||
feedback.classList.remove("alert-secondary", "alert-success");
|
||||
feedback.classList.add("alert-danger");
|
||||
feedback.textContent = safeCommandErrorText(caughtError, "La reprise du checkpoint a été refusée.");
|
||||
}
|
||||
frontendWarn("main", "Backfill Desk in-session Resume failed");
|
||||
}
|
||||
}
|
||||
@@ -597,7 +684,14 @@ async function bindBackfillStatusMonitoring(): Promise<void> {
|
||||
});
|
||||
return syncBackfillStatus("cancel");
|
||||
})
|
||||
.catch(() => {
|
||||
.catch(caughtError => {
|
||||
const feedback = document.querySelector<HTMLElement>("#backfillCancelFeedback");
|
||||
if (feedback) {
|
||||
feedback.hidden = false;
|
||||
feedback.classList.remove("alert-secondary", "alert-success");
|
||||
feedback.classList.add("alert-danger");
|
||||
feedback.textContent = safeCommandErrorText(caughtError, "La demande d'annulation a été refusée.");
|
||||
}
|
||||
frontendWarn("main", "Backfill Desk cooperative cancellation failed");
|
||||
if (currentRunStatus?.active) {
|
||||
cancelButton.disabled = false;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/src/app_state.rs
|
||||
// version: 9
|
||||
// version: 10
|
||||
|
||||
//! Shared backend state owned by the Backfill Desk Tauri application.
|
||||
|
||||
@@ -123,7 +123,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.011-resume-checkpoint".to_owned(),
|
||||
shell_phase: "pre.012-frontend-polish".to_owned(),
|
||||
startup_diagnostic: runtime.startup_diagnostic.clone(),
|
||||
});
|
||||
}
|
||||
@@ -146,6 +146,7 @@ impl crate::AppState {
|
||||
http_routes: std::vec::Vec::new(),
|
||||
limits,
|
||||
network_coherent: false,
|
||||
program_id_options: crate::program_id_autocomplete_options(),
|
||||
scope_kinds: crate::backfill_scope_kind_codes(),
|
||||
store_diagnostic: std::option::Option::None,
|
||||
store_network: std::option::Option::None,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/src/dto_common.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
//! Common Tauri DTOs shared by the Backfill Desk shell.
|
||||
|
||||
@@ -21,6 +21,45 @@ pub(crate) struct BackfillHttpRouteOptionDto {
|
||||
pub(crate) role: String,
|
||||
}
|
||||
|
||||
/// Safe Program ID entry exposed only to populate the free-form address autocomplete dataset.
|
||||
///
|
||||
/// Every value is derived from the canonical `ksp-core-lib` registry. The frontend remains free to submit any valid Solana address; this DTO is advisory only.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_backfill_desk/dto_common/ProgramIdAutocompleteOptionDto.ts")]
|
||||
pub(crate) struct ProgramIdAutocompleteOptionDto {
|
||||
/// Stable KSP machine-readable Program ID code.
|
||||
pub(crate) code: String,
|
||||
/// Broad functional domain from the canonical registry.
|
||||
pub(crate) domain: String,
|
||||
/// Functional family from the canonical registry.
|
||||
pub(crate) family: String,
|
||||
/// Human-readable program name.
|
||||
pub(crate) name: String,
|
||||
/// Canonical Base58 Program ID used as the HTML datalist value.
|
||||
pub(crate) program_id: String,
|
||||
/// Owning protocol/project label from the canonical registry.
|
||||
pub(crate) protocol: String,
|
||||
}
|
||||
|
||||
/// Projects the canonical KSP Program ID registry to the safe autocomplete dataset.
|
||||
#[must_use]
|
||||
pub(crate) fn program_id_autocomplete_options() -> std::vec::Vec<ProgramIdAutocompleteOptionDto> {
|
||||
let entries = ksp_core_lib::entries();
|
||||
let mut options = std::vec::Vec::with_capacity(entries.len());
|
||||
for entry in entries {
|
||||
options.push(ProgramIdAutocompleteOptionDto {
|
||||
code: entry.code().to_owned(),
|
||||
domain: entry.domain().to_owned(),
|
||||
family: entry.family().to_owned(),
|
||||
name: entry.name().to_owned(),
|
||||
program_id: entry.program_id().to_owned(),
|
||||
protocol: entry.protocol().to_owned(),
|
||||
});
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
/// Safe Backfill Desk options contract combining readiness, HTTP routes and Job-owned campaign bounds.
|
||||
///
|
||||
/// Endpoint URLs, credentials and physical routing details are intentionally absent.
|
||||
@@ -36,6 +75,8 @@ pub(crate) struct BackfillDeskOptionsDto {
|
||||
pub(crate) limits: crate::BackfillRequestLimitsDto,
|
||||
/// Whether the Transport and Store readiness gates jointly permit later Backfill composition.
|
||||
pub(crate) composition_ready: bool,
|
||||
/// Canonical KSP Program IDs exposed as a frontend autocomplete dataset; never an allow-list.
|
||||
pub(crate) program_id_options: std::vec::Vec<ProgramIdAutocompleteOptionDto>,
|
||||
/// Distinct enabled HTTP cluster labels selected by the active Transport profile.
|
||||
pub(crate) configured_networks: std::vec::Vec<String>,
|
||||
/// Whether the selected Store network exactly matches the one coherent Transport network.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/src/lib.rs
|
||||
// version: 10
|
||||
// version: 11
|
||||
|
||||
//! Tauri desktop application scaffold for controlling and inspecting KSP RAW backfill jobs.
|
||||
|
||||
@@ -116,8 +116,12 @@ pub(crate) use self::dto_common::BackfillDeskOptionsDto;
|
||||
pub(crate) use self::dto_common::BackfillHttpRouteOptionDto;
|
||||
/// Safe command error projection exposed to Tauri commands.
|
||||
pub(crate) use self::dto_common::CommandErrorDto;
|
||||
/// Safe canonical Program ID entry exposed only for free-form address autocomplete.
|
||||
pub(crate) use self::dto_common::ProgramIdAutocompleteOptionDto;
|
||||
/// Safe scaffold/runtime snapshot exposed to the shell.
|
||||
pub(crate) use self::dto_common::ShellStatusDto;
|
||||
/// Projects the canonical Core Program ID registry to autocomplete entries.
|
||||
pub(crate) use self::dto_common::program_id_autocomplete_options;
|
||||
/// Shared Backfill Desk runtime state is internally inconsistent.
|
||||
pub(crate) use self::errors::ERROR_CODE_APP_STATE_INVALID;
|
||||
/// Shared Backfill Desk runtime state cannot be locked safely.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/src/transport_runtime.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
//! Composite-selected HTTP Transport readiness and route inventory owned by Backfill Desk.
|
||||
|
||||
@@ -54,6 +54,7 @@ impl TransportRuntime {
|
||||
http_routes,
|
||||
limits,
|
||||
network_coherent: false,
|
||||
program_id_options: crate::program_id_autocomplete_options(),
|
||||
scope_kinds: crate::backfill_scope_kind_codes(),
|
||||
store_diagnostic: std::option::Option::None,
|
||||
store_network: std::option::Option::None,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/tests/desktop_contract.rs
|
||||
// version: 13
|
||||
// version: 14
|
||||
|
||||
//! Structural desktop contract checks for the Backfill Desk scaffold.
|
||||
|
||||
@@ -404,3 +404,33 @@ fn pre_011_resume_reissues_rust_only_checkpoint_for_new_backend_job_without_ipc_
|
||||
assert!(html.contains("resumeBackfillRun"));
|
||||
assert!(html.contains("Reprendre"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_012_free_address_autocomplete_is_html_datalist_derived_from_core_registry() {
|
||||
let root = app_root();
|
||||
let dto = read_text(root.join("src/dto_common.rs").as_path());
|
||||
assert!(dto.contains("ksp_core_lib::entries()"));
|
||||
assert!(dto.contains("ProgramIdAutocompleteOptionDto"));
|
||||
assert!(dto.contains("program_id_autocomplete_options"));
|
||||
let html = read_text(root.join("frontend/main.html").as_path());
|
||||
assert!(html.contains("list=\"backfillProgramIds\""));
|
||||
assert!(html.contains("<datalist id=\"backfillProgramIds\"></datalist>"));
|
||||
assert!(html.contains("toute adresse Solana valide reste acceptée"));
|
||||
let frontend = read_text(root.join("frontend/ts/main.ts").as_path());
|
||||
for required in [
|
||||
"renderProgramIdAutocomplete(options.programIdOptions)",
|
||||
"option.value = program.programId",
|
||||
"option.dataset.code = program.code",
|
||||
"option.dataset.domain = program.domain",
|
||||
"registryMatch: selected !== null",
|
||||
"Backfill Desk Program ID autocomplete dataset rendered",
|
||||
] {
|
||||
assert!(frontend.contains(required), "missing pre.012 autocomplete marker {required}");
|
||||
}
|
||||
for forbidden in
|
||||
["AddressLookupTab1e1111111111111111111111111", "ComputeBudget111111111111111111111111111111", "Vote111111111111111111111111111111111111111"]
|
||||
{
|
||||
assert!(!frontend.contains(forbidden), "frontend hardcodes canonical Program ID {forbidden}");
|
||||
assert!(!html.contains(forbidden), "HTML hardcodes canonical Program ID {forbidden}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/tests/desktop_security.rs
|
||||
// version: 14
|
||||
// version: 15
|
||||
|
||||
//! Security and dependency-boundary checks for the Backfill Desk scaffold.
|
||||
|
||||
@@ -339,3 +339,29 @@ fn pre_011_resume_surface_exposes_only_new_job_acknowledgement_and_never_checkpo
|
||||
assert!(!resume_source.contains(forbidden), "Resume frontend sends or logs forbidden payload marker {forbidden}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_012_program_id_autocomplete_exposes_only_public_registry_metadata_and_no_browser_storage() {
|
||||
let root = app_root();
|
||||
let dto_source = read_text(root.join("src/dto_common.rs").as_path());
|
||||
let dto = struct_source(dto_source.as_str(), "ProgramIdAutocompleteOptionDto");
|
||||
for required in ["code", "domain", "family", "name", "program_id", "protocol"] {
|
||||
assert!(dto.contains(required), "Program ID autocomplete DTO missing safe registry field {required}");
|
||||
}
|
||||
for forbidden in ["url", "credential", "token", "secret", "endpoint", "connection", "private_key"] {
|
||||
assert!(!dto.contains(forbidden), "Program ID autocomplete DTO leaks forbidden marker {forbidden}");
|
||||
}
|
||||
let frontend = read_text(root.join("frontend/ts/main.ts").as_path());
|
||||
for forbidden in ["localStorage", "sessionStorage", "indexedDB", "document.cookie"] {
|
||||
assert!(!frontend.contains(forbidden), "frontend persists campaign/autocomplete material via {forbidden}");
|
||||
}
|
||||
let selection_marker = frontend.find("Backfill Desk address autocomplete selection evaluated");
|
||||
assert!(selection_marker.is_some());
|
||||
let selection_source = match selection_marker {
|
||||
std::option::Option::Some(index) => &frontend[index..std::cmp::min(index + 320, frontend.len())],
|
||||
std::option::Option::None => "",
|
||||
};
|
||||
assert!(selection_source.contains("programCode"));
|
||||
assert!(selection_source.contains("registryMatch"));
|
||||
assert!(!selection_source.contains("address.value"));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/unit_tests/backfill_request.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
fn ready_options() -> crate::BackfillDeskOptionsDto {
|
||||
let limits = crate::backfill_request_limits();
|
||||
@@ -33,6 +33,7 @@ fn ready_options() -> crate::BackfillDeskOptionsDto {
|
||||
],
|
||||
limits,
|
||||
network_coherent: true,
|
||||
program_id_options: crate::program_id_autocomplete_options(),
|
||||
scope_kinds: crate::backfill_scope_kind_codes(),
|
||||
store_diagnostic: std::option::Option::None,
|
||||
store_network: std::option::Option::Some("mainnet-beta".to_owned()),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/unit_tests/dto_common.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
#[test]
|
||||
fn command_error_projection_excludes_arbitrary_context_and_source_values() {
|
||||
@@ -28,6 +28,7 @@ fn transport_options_projection_contains_only_safe_transport_metadata() {
|
||||
composition_ready: true,
|
||||
configured_networks: vec!["devnet".to_owned()],
|
||||
network_coherent: true,
|
||||
program_id_options: crate::program_id_autocomplete_options(),
|
||||
scope_kinds: crate::backfill_scope_kind_codes(),
|
||||
store_diagnostic: std::option::Option::None,
|
||||
store_network: std::option::Option::Some("devnet".to_owned()),
|
||||
@@ -49,3 +50,18 @@ fn transport_options_projection_contains_only_safe_transport_metadata() {
|
||||
assert!(!serialized.contains("database"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn program_id_autocomplete_dataset_is_derived_exactly_from_core_registry() {
|
||||
let options = crate::program_id_autocomplete_options();
|
||||
let entries = ksp_core_lib::entries();
|
||||
assert_eq!(options.len(), entries.len());
|
||||
for (option, entry) in options.iter().zip(entries.iter()) {
|
||||
assert_eq!(option.code, entry.code());
|
||||
assert_eq!(option.name, entry.name());
|
||||
assert_eq!(option.program_id, entry.program_id());
|
||||
assert_eq!(option.domain, entry.domain());
|
||||
assert_eq!(option.family, entry.family());
|
||||
assert_eq!(option.protocol, entry.protocol());
|
||||
}
|
||||
}
|
||||
|
||||
130
deltas/0.3.7/pre.012.md
Normal file
130
deltas/0.3.7/pre.012.md
Normal file
@@ -0,0 +1,130 @@
|
||||
<!-- file: deltas/0.3.7/pre.012.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta `0.3.7-pre.012` — frontend fonctionnel, summary terminal et autocomplete Program ID
|
||||
|
||||
## 1. Base requise
|
||||
|
||||
```text
|
||||
0.3.7-pre.011
|
||||
workspace.package.version = 0.3.7-pre.11
|
||||
```
|
||||
|
||||
## 2. Objectif
|
||||
|
||||
Terminer le frontend fonctionnel sans ouvrir de nouveau runtime : enrichir les feedbacks terminal/erreurs et ajouter à l'adresse un autocomplete HTML libre alimenté exclusivement par le registre canonique `ksp-core-lib::entries()`/`ProgramIdEntry`.
|
||||
|
||||
## 3. Dataset Program ID canonique
|
||||
|
||||
`ProgramIdAutocompleteOptionDto` projette uniquement les métadonnées publiques nécessaires à l'UX :
|
||||
|
||||
```text
|
||||
program_id
|
||||
code
|
||||
name
|
||||
domain
|
||||
family
|
||||
protocol
|
||||
```
|
||||
|
||||
`program_id_autocomplete_options()` parcourt directement `ksp_core_lib::entries()`. Aucune liste de Program IDs n'est copiée dans Backfill Desk ni dans le frontend.
|
||||
|
||||
`BackfillDeskOptionsDto` transporte ce dataset sûr avec les autres options de campagne. Aucun endpoint, URL, credential, secret ou donnée Store n'est ajouté.
|
||||
|
||||
## 4. Autocomplete libre côté HTML
|
||||
|
||||
L'input `backfillAddress` reste un `<input type="text">` libre et référence :
|
||||
|
||||
```text
|
||||
<datalist id="backfillProgramIds">
|
||||
```
|
||||
|
||||
Le frontend construit chaque `<option>` avec le Program ID canonique comme `value`, un label lisible et des `data-*` sûrs (`code`, `name`, `domain`, `family`, `protocol`). Une adresse valide qui n'appartient pas au registre reste acceptée normalement par le mapping Rust existant.
|
||||
|
||||
L'instrumentation de sélection ne journalise jamais l'adresse : seulement le code KSP éventuel et `registryMatch`. Aucun browser storage n'est introduit.
|
||||
|
||||
## 5. Polish monitoring et erreurs
|
||||
|
||||
La carte latest-value affiche désormais un résumé terminal explicite pour `completed`, `cancelled` ou `failed`. Les échecs Cancel et Resume sont projetés dans leurs feedbacks UI via le contrat d'erreur sûr déjà existant, sans contexte arbitraire ni payload métier.
|
||||
|
||||
Aucun comportement Job/Store/Transport, checkpoint, Resume ou Cancel n'est modifié.
|
||||
|
||||
## 6. Version
|
||||
|
||||
```text
|
||||
workspace.package.version = 0.3.7-pre.12
|
||||
label = 0.3.7-pre.012
|
||||
```
|
||||
|
||||
## 7. Fichiers ajoutés
|
||||
|
||||
```text
|
||||
deltas/0.3.7/pre.012.md
|
||||
```
|
||||
|
||||
## 8. Fichiers modifiés
|
||||
|
||||
```text
|
||||
Cargo.toml
|
||||
crates/ksp-app-backfill-desk/frontend/main.html
|
||||
crates/ksp-app-backfill-desk/frontend/ts/main.ts
|
||||
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/tests/desktop_contract.rs
|
||||
crates/ksp-app-backfill-desk/tests/desktop_security.rs
|
||||
crates/ksp-app-backfill-desk/unit_tests/backfill_request.rs
|
||||
crates/ksp-app-backfill-desk/unit_tests/dto_common.rs
|
||||
docs/plans/028-V0_3_7_BACKFILL_DESK_PLAN.md
|
||||
docs/validation/024-V0_3_7_BACKFILL_DESK.md
|
||||
```
|
||||
|
||||
## 9. Fichiers supprimés
|
||||
|
||||
Aucun.
|
||||
|
||||
## 10. Gate précédent acquis
|
||||
|
||||
Le replay opérateur de `0.3.7-pre.011` est propre : audits Rust/Markdown, `cargo check --workspace`, Clippy, 50 tests unitaires Job Backfill et toutes les suites Backfill Desk passent. Le smoke Tauri confirme également la reprise in-session réelle avec nouveau JobId backend et checkpoint réémis Rust-only.
|
||||
|
||||
## 11. Validations exécutées dans l'environnement d'assemblage
|
||||
|
||||
```text
|
||||
python3 scripts/audit_rust_workspace_rules.py
|
||||
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.3.7
|
||||
```
|
||||
|
||||
Contrôles ciblés :
|
||||
|
||||
- dataset dérivé exclusivement de `ksp_core_lib::entries()` ;
|
||||
- aucune valeur Program ID canonique hardcodée dans HTML/TypeScript ;
|
||||
- `<datalist>` relié à un input adresse toujours libre ;
|
||||
- `data-*` limités aux métadonnées publiques du registre ;
|
||||
- aucune adresse journalisée pendant l'évaluation autocomplete ;
|
||||
- aucun `localStorage`, `sessionStorage`, IndexedDB ou cookie ajouté ;
|
||||
- summary terminal et feedbacks Cancel/Resume sûrs ;
|
||||
- aucune dépendance/feature Cargo modifiée ;
|
||||
- `std.store.json` inchangé ;
|
||||
- syntaxe TypeScript validée par transpilation locale ;
|
||||
- contrôle différentiel des headers et reconstruction stricte du delta.
|
||||
|
||||
## 12. Validations non exécutées dans l'environnement d'assemblage
|
||||
|
||||
`cargo` et `rustfmt` ne sont pas disponibles dans l'environnement d'assemblage. Gate opérateur obligatoire avant `pre.013` :
|
||||
|
||||
```text
|
||||
cargo fmt --all
|
||||
python3 scripts/audit_rust_workspace_rules.py
|
||||
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.3.7
|
||||
cargo check --workspace
|
||||
cargo clippy --workspace --all-targets
|
||||
cargo test -p ksp-job-backfill-lib
|
||||
cargo test -p ksp-app-backfill-desk
|
||||
```
|
||||
|
||||
Aucun `cargo tree` n'est requis : dépendances et features Cargo sont inchangées.
|
||||
|
||||
## 13. Suite
|
||||
|
||||
`pre.013` est réservé au hardening/completude : tests desktop/security/dependency/composition/release-completeness et canaries DTO, sans nouvelle capacité métier.
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/plans/028-V0_3_7_BACKFILL_DESK_PLAN.md -->
|
||||
<!-- version: 11 -->
|
||||
<!-- version: 12 -->
|
||||
|
||||
# Plan v0.3.7 — Backfill Desk
|
||||
|
||||
@@ -292,6 +292,8 @@ La commande `backfill_resume()` alloue un nouveau JobId backend, refuse un run a
|
||||
|
||||
Formulaire complet, états responsive, instrumentation sûre, summary terminal, erreurs, boutons contextuels, aucun secret/browser storage. L'adresse reste une saisie libre, mais reçoit un autocomplete HTML via `datalist` alimenté depuis le registre canonique `ksp-core-lib::entries()`/`ProgramIdEntry` : aucune copie locale de Program IDs, et une valeur arbitraire valide reste toujours acceptée. Le comportement fonctionnel recherché est celui de l'ancien dataset `ks-program-ids` de kbot3, sans reprise de son code.
|
||||
|
||||
La tranche matérialise ce contrat via `ProgramIdAutocompleteOptionDto`, ajouté à `BackfillDeskOptionsDto`. Chaque `<option>` du `datalist` reçoit le Program ID canonique comme `value` et des `data-*` sûrs (`code`, `name`, `domain`, `family`, `protocol`) uniquement pour l'UX. Une sélection issue du registre ne journalise que le code KSP et le booléen de correspondance, jamais l'adresse saisie. La carte de monitoring reçoit en plus un résumé terminal explicite et les échecs Cancel/Resume sont projetés dans des feedbacks sûrs sans contexte arbitraire.
|
||||
|
||||
### pre.013 — hardening et complétude
|
||||
|
||||
Tests desktop/security/dependency/composition/release completeness, canaries DTO et scan des dépendances/interdictions.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/validation/024-V0_3_7_BACKFILL_DESK.md -->
|
||||
<!-- version: 19 -->
|
||||
<!-- version: 20 -->
|
||||
|
||||
# Validation v0.3.7 — Backfill Desk
|
||||
|
||||
@@ -525,5 +525,18 @@ Le frontend expose **Reprendre** uniquement pour un snapshot terminal avec `chec
|
||||
- [X] frontend Resume sans checkpoint ni payload de campagne ;
|
||||
- [X] aucun fichier/table/JSON de checkpoint durable ajouté ;
|
||||
- [X] aucune dépendance/feature Cargo ajoutée ;
|
||||
- [ ] `cargo fmt/check/clippy/test` de `pre.011` à rejouer par l'opérateur.
|
||||
- [X] replay opérateur `cargo fmt/check/clippy/test` de `pre.011` propre : 50 tests unitaires Job Backfill et toutes les suites Backfill Desk passent.
|
||||
|
||||
Le smoke Tauri `pre.011` confirme également le Resume réel : après un timeout Transport sur un run terminal, `backfill_resume` réémet le checkpoint Rust-only vers un nouveau JobId backend, le nouveau run est admis et repart sur le même rôle HTTP ; un Cancel ciblé ultérieur est accepté puis terminalise proprement le run repris.
|
||||
|
||||
## Pre.012 — frontend fonctionnel, summary terminal et autocomplete Program ID
|
||||
|
||||
- [X] `BackfillDeskOptionsDto` expose un dataset Program ID dérivé exclusivement de `ksp-core-lib::entries()`.
|
||||
- [X] `ProgramIdAutocompleteOptionDto` ne contient que `program_id`, `code`, `name`, `domain`, `family` et `protocol`.
|
||||
- [X] L'input `backfillAddress` reste un champ texte libre et référence `backfillProgramIds` via l'attribut HTML `list`.
|
||||
- [X] Le frontend construit le `datalist` sans Program ID hardcodé et ajoute des `data-*` sûrs pour l'UX.
|
||||
- [X] La sélection autocomplete ne journalise jamais l'adresse, seulement le code KSP éventuel et un booléen `registryMatch`.
|
||||
- [X] Aucun `localStorage`, `sessionStorage`, IndexedDB ou cookie n'est ajouté.
|
||||
- [X] Le monitoring affiche un résumé terminal explicite et les erreurs Cancel/Resume sont rendues via la projection d'erreur sûre.
|
||||
- [X] Aucune dépendance/feature Cargo, aucun Store/Transport/Job runtime et aucun `std.store.json` modifié.
|
||||
- [ ] Gate opérateur `cargo fmt` + audits + check/clippy/tests Backfill à rejouer avant `pre.013`.
|
||||
|
||||
Reference in New Issue
Block a user