Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
dd8d30b
app: add backup and restore product flow model
KeyffMS Sep 10, 2026
f25f168
app: expose backup restore through application boundary
KeyffMS Sep 10, 2026
a7a5bd6
app: route public application API through product flow
KeyffMS Sep 10, 2026
1c85b0f
runtime: integrate backup capture and guarded restore
KeyffMS Sep 10, 2026
8ae57e5
app: preserve legacy unit-test surface while routing product API
KeyffMS Sep 10, 2026
0f3c841
runtime: route backup effects through guarded operation runtime
KeyffMS Sep 10, 2026
0c8cf87
tui: wire product build metadata and backup runtime path
KeyffMS Sep 10, 2026
dff4328
tui: add backup restore presentation state
KeyffMS Sep 10, 2026
7e9dd90
tui: render functional backup diff restore screen
KeyffMS Sep 10, 2026
f74c453
tui: add guarded backup restore keymap
KeyffMS Sep 10, 2026
10446ea
tui: add exact restore confirmation input state
KeyffMS Sep 10, 2026
8f7ebbc
tui: route backup screen to functional renderer
KeyffMS Sep 10, 2026
bda9a29
tui: dispatch backup restore keys before global keymap
KeyffMS Sep 10, 2026
7165910
runtime: implement production restore session adapter
KeyffMS Sep 10, 2026
92ffcc7
app: clean backup restore product wrapper imports
KeyffMS Sep 10, 2026
6c11a2d
tui: preserve existing global UI state semantics
KeyffMS Sep 10, 2026
d45936b
tui: reuse existing exact confirmation form for restore
KeyffMS Sep 10, 2026
a201587
tui: reuse existing form reducer for restore confirmation
KeyffMS Sep 10, 2026
4e8fe97
tui: keep backup integration on existing UI state
KeyffMS Sep 10, 2026
e88a4c6
tui: remove redundant backup input state
KeyffMS Sep 10, 2026
b3df3f5
style: apply rustfmt to backup restore application flow
KeyffMS Sep 10, 2026
ac906af
style: apply rustfmt to backup restore keymap
KeyffMS Sep 10, 2026
d67a46c
style: apply rustfmt to backup renderer
KeyffMS Sep 10, 2026
7d7defb
style: apply rustfmt to backup restore runtime
KeyffMS Sep 10, 2026
c121a4c
app: box large backup restore payloads
KeyffMS Sep 10, 2026
43dd7a5
app: keep product action and effect payloads bounded
KeyffMS Sep 10, 2026
4035b4e
app: keep legacy unit tests isolated from product wrapper
KeyffMS Sep 10, 2026
abaef31
runtime: unwrap bounded backup effects at runner boundary
KeyffMS Sep 10, 2026
75da3e6
app: box startup backup catalog action
KeyffMS Sep 10, 2026
6deda48
tui: box backup action payloads
KeyffMS Sep 10, 2026
a2602a7
runtime: adapt guarded backup flow to boxed product boundary
KeyffMS Sep 10, 2026
c47cb6f
style: apply rustfmt ordering to application exports
KeyffMS Sep 10, 2026
38cbbda
test: scope write runtime adapter constructor to tests
KeyffMS Sep 10, 2026
81ff5b4
storage: own backup catalog filesystem access
KeyffMS Sep 10, 2026
47f5871
storage: export deterministic backup catalog
KeyffMS Sep 10, 2026
4cc99ec
runtime: delegate backup catalog IO to storage adapter
KeyffMS Sep 10, 2026
ec1a3a7
style: apply rustfmt to backup catalog
KeyffMS Sep 10, 2026
de9b1ce
tui: preserve global Ctrl-C during restore confirmation
KeyffMS Sep 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 175 additions & 0 deletions crates/lantern-app/src/backup_flow.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
use std::path::PathBuf;

use lantern_domain::{BackupDiffStatus, BackupDifference, BackupSnapshot, DeviceWriteOutcome};

use crate::{ApprovedRestorePlan, BackupCaptureContext, RestoreConfirmation};

#[derive(Clone, Debug)]
pub struct StoredBackup {
pub path: PathBuf,
pub snapshot: BackupSnapshot,
}

#[derive(Clone, Debug)]
pub struct PreparedRestoreBundle {
pub pre_restore: StoredBackup,
pub diff: Vec<BackupDifference>,
pub plan: ApprovedRestorePlan,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RestoreExecutionSummary {
pub attempted_steps: usize,
pub verified_steps: usize,
pub terminal_outcome: Option<DeviceWriteOutcome>,
}

#[derive(Clone, Debug)]
pub enum BackupAction {
RefreshCatalog,
CatalogRefreshed(Result<Vec<PathBuf>, String>),
Capture,
Captured(Result<StoredBackup, String>),
SelectSource(PathBuf),
SourceLoaded {
path: PathBuf,
result: Result<BackupSnapshot, String>,
},
ClearSource,
PrepareRestore,
RestorePrepared(Box<Result<PreparedRestoreBundle, String>>),
ConfirmRestore {
operator_text: String,
},
RestoreCompleted(Result<RestoreExecutionSummary, String>),
}

#[derive(Clone, Debug)]
pub enum BackupEffect {
RefreshCatalog,
Capture {
context: BackupCaptureContext,
},
LoadSource {
path: PathBuf,
},
PrepareRestore {
source: Box<BackupSnapshot>,
context: BackupCaptureContext,
},
ExecuteRestore {
plan: ApprovedRestorePlan,
confirmation: RestoreConfirmation,
},
}

#[derive(Clone, Debug, Default)]
pub(crate) struct BackupRestoreState {
pub catalog: Vec<PathBuf>,
pub last_capture: Option<StoredBackup>,
pub source: Option<StoredBackup>,
pub pre_restore: Option<StoredBackup>,
pub diff: Vec<BackupDifference>,
pub prepared_plan: Option<ApprovedRestorePlan>,
pub status: Option<String>,
pub error: Option<String>,
}

impl BackupRestoreState {
pub(crate) fn invalidate_prepared_operation(&mut self) {
self.pre_restore = None;
self.diff.clear();
self.prepared_plan = None;
}

pub(crate) fn view(&self) -> BackupRestoreView {
BackupRestoreView {
catalog: self.catalog.clone(),
last_capture: self.last_capture.as_ref().map(backup_summary),
source: self.source.as_ref().map(backup_summary),
pre_restore: self.pre_restore.as_ref().map(backup_summary),
diff: self
.diff
.iter()
.map(|entry| BackupDiffEntryView {
parameter_id: entry.parameter_id.as_str().to_owned(),
status: entry.status,
})
.collect(),
prepared_plan: self.prepared_plan.as_ref().map(|plan| RestorePlanView {
plan_hash: plan.plan_hash().to_owned(),
challenge: plan.operator_confirmation_text(),
steps: plan
.steps()
.iter()
.map(|step| RestoreStepView {
index: step.index(),
parameter_id: step.parameter_id().as_str().to_owned(),
expected_old: format!("{:?}", step.expected_old_raw().as_slice()),
target: format!("{:?}", step.target_raw().as_slice()),
})
.collect(),
skipped: plan.skipped().len(),
}),
status: self.status.clone(),
error: self.error.clone(),
}
}
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BackupSummaryView {
pub path: PathBuf,
pub backup_id: u128,
pub complete: bool,
pub profile_id: String,
pub profile_hash: String,
pub values: usize,
pub errors: usize,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BackupDiffEntryView {
pub parameter_id: String,
pub status: BackupDiffStatus,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RestoreStepView {
pub index: usize,
pub parameter_id: String,
pub expected_old: String,
pub target: String,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RestorePlanView {
pub plan_hash: String,
pub challenge: String,
pub steps: Vec<RestoreStepView>,
pub skipped: usize,
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct BackupRestoreView {
pub catalog: Vec<PathBuf>,
pub last_capture: Option<BackupSummaryView>,
pub source: Option<BackupSummaryView>,
pub pre_restore: Option<BackupSummaryView>,
pub diff: Vec<BackupDiffEntryView>,
pub prepared_plan: Option<RestorePlanView>,
pub status: Option<String>,
pub error: Option<String>,
}

fn backup_summary(stored: &StoredBackup) -> BackupSummaryView {
BackupSummaryView {
path: stored.path.clone(),
backup_id: stored.snapshot.backup_id.get(),
complete: stored.snapshot.is_complete(),
profile_id: stored.snapshot.profile_id.as_str().to_owned(),
profile_hash: stored.snapshot.profile_hash.clone(),
values: stored.snapshot.values.len(),
errors: stored.snapshot.errors.len(),
}
}
16 changes: 16 additions & 0 deletions crates/lantern-app/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

mod application;
mod backup;
#[cfg(not(test))]
mod backup_flow;
mod bus;
mod clock;
mod connection;
Expand All @@ -17,6 +19,8 @@ mod monitoring_projection;
mod parameters;
mod poll;
mod ports;
#[cfg(not(test))]
mod product_application;
mod profile_registry;
mod restore;
mod restore_permit;
Expand All @@ -27,8 +31,18 @@ mod telemetry;
mod write_coordinator;
mod write_flow;

#[cfg(test)]
pub use application::*;
#[cfg(not(test))]
#[doc(hidden)]
pub use application::{
ApplicationRuntime as LegacyApplicationRuntime, EffectRunner as LegacyEffectRunner,
};
#[cfg(not(test))]
pub use application::{AuditHealthView, AuthorizationView, OperationView};
pub use backup::*;
#[cfg(not(test))]
pub use backup_flow::*;
pub use bus::*;
pub use clock::*;
pub use connection::*;
Expand All @@ -54,6 +68,8 @@ pub use monitoring_projection::*;
pub use parameters::*;
pub use poll::*;
pub use ports::*;
#[cfg(not(test))]
pub use product_application::*;
pub use profile_registry::*;
pub use restore::*;
pub use restore_permit::*;
Expand Down
Loading
Loading