Add explicit vocabulary and named operation contexts

This commit is contained in:
2026-10-29 22:41:38 +02:00
parent ede45d224a
commit 8413b382b1
14 changed files with 344 additions and 241 deletions
+43 -53
View File
@@ -19,15 +19,12 @@ use crate::delivery::files::{self, FileOutputError, PreparedDestination};
use crate::delivery::qr;
use crate::delivery::{self, ClientConfigPayload, DeliverySettings, QrCodeOutput, QrError};
use crate::domain::{
AddressAvailability, Client, ClientAddresses, ClientStates, DomainError, PublicKey,
RuntimeObservation, Snapshot, allocate_ipv4, allocate_ipv6, validate_manual_ipv4,
validate_manual_ipv6,
AddressAvailability, ApplyStatus, Client, ClientAddresses, ClientStates, DomainError,
PublicKey, Snapshot, allocate_ipv4, allocate_ipv6, validate_manual_ipv4, validate_manual_ipv6,
};
use crate::persistence::{BackupComplete, PersistedState, PersistenceError, SavedStore};
use crate::runtime::transport::TransportError;
use crate::runtime::{
self, RuntimeObservation as TechnicalObservation, SavedPeerProjection, TechnicalMatch,
};
use crate::runtime::{self, RuntimeObservation, SavedPeerProjection, TechnicalMatch};
use crate::service::{self, RestartDeadline, RestartExecution, RestartOutcome};
use crate::vault::source::{
PreparedImportSetup, SelectedFileImportReport, SourceImportError, SourceImportReport,
@@ -504,7 +501,7 @@ impl Application {
let states = ClientStates::new(
snapshot_from_document(&document),
None,
RuntimeObservation::Unknown,
ApplyStatus::Unknown,
)?;
Self::open_saved(store, saved, states)
}
@@ -984,7 +981,7 @@ impl Application {
let Ok((projection, _)) = restart_projection_from_document(document) else {
return false;
};
self.states.applied() == &RuntimeObservation::Verified
self.states.applied() == &ApplyStatus::Verified
&& self.verified_saved_projection.as_ref() == Some(&projection)
}
@@ -1487,7 +1484,7 @@ impl Application {
preview: RestartPreview,
choice: RestartChoice,
service: impl FnOnce(&str, RestartDeadline) -> RestartExecution,
runtime_read: impl FnOnce(&str, Duration) -> Result<TechnicalObservation, TransportError>,
runtime_read: impl FnOnce(&str, Duration) -> Result<RuntimeObservation, TransportError>,
install: impl FnOnce(&SavedStore, &SavedBytes, BackupComplete) -> Result<(), PersistenceError>,
) -> RestartReport {
self.restart_with_adapters_and_clock(
@@ -1532,7 +1529,7 @@ impl Application {
preview: RestartPreview,
choice: RestartChoice,
service: impl FnOnce(&str, RestartDeadline) -> RestartExecution,
runtime_read: impl FnOnce(&str, Duration) -> Result<TechnicalObservation, TransportError>,
runtime_read: impl FnOnce(&str, Duration) -> Result<RuntimeObservation, TransportError>,
install: impl FnOnce(&SavedStore, &SavedBytes, BackupComplete) -> Result<(), PersistenceError>,
now: impl Fn() -> Instant,
) -> RestartReport {
@@ -1860,7 +1857,7 @@ impl Application {
let mut proposed = preview.saved_bytes;
let document = ConfigDocument::parse(&proposed.server_config)?;
let snapshot = snapshot_from_document(&document);
let mut restored_states = ClientStates::new(snapshot, None, RuntimeObservation::Unknown)?;
let mut restored_states = ClientStates::new(snapshot, None, ApplyStatus::Unknown)?;
let mut archived_reservations = BTreeSet::new();
let mut reservations = self.states.reserved_addresses().clone();
// A restore can remove clients that are currently saved even when their
@@ -1947,7 +1944,7 @@ impl Application {
.map(VaultBundle::from_toml)
.transpose()?;
let snapshot = snapshot_from_document(&document);
let mut states = ClientStates::new(snapshot, None, RuntimeObservation::Unknown)?;
let mut states = ClientStates::new(snapshot, None, ApplyStatus::Unknown)?;
if let Some(bytes) = &preview.saved_bytes.internal_state {
PersistedState::from_toml(bytes)?.apply_to(&mut states)?;
}
@@ -2508,16 +2505,14 @@ mod tests {
use crate::backup::{BackupReason, HistoricalApplyStatus, create_archive, preview_archive};
use crate::config::SettingsFile;
use crate::delivery::qr::QrError;
use crate::domain::{
Client, ClientAddresses, ClientStates, PublicKey, RuntimeObservation, Snapshot,
};
use crate::domain::{ApplyStatus, Client, ClientAddresses, ClientStates, PublicKey, Snapshot};
use crate::persistence::{
BackupComplete, InstallCheckpoint, PersistedState, PersistenceError, SavedStore,
};
use crate::runtime::transport::TransportError;
use crate::runtime::{
InterfaceObservation, Observed, PeerSetCompleteness, RuntimeObservation as ObservedRuntime,
RuntimePeer, RuntimePeerSet,
InterfaceObservation, Observed, PeerSetCompleteness, RuntimeObservation, RuntimePeer,
RuntimePeerSet,
};
use crate::service::{RestartExecution, RestartFailure, RestartOutcome};
use crate::vault::{VaultBundle, WireGuardPrivateKey};
@@ -2586,12 +2581,8 @@ restart_timeout_seconds = 30
)
.expect("state client valid");
let snapshot = Snapshot::new([saved_client]).expect("snapshot valid");
let states = ClientStates::new(
snapshot.clone(),
Some(snapshot),
RuntimeObservation::Verified,
)
.expect("initial applied state valid");
let states = ClientStates::new(snapshot.clone(), Some(snapshot), ApplyStatus::Verified)
.expect("initial applied state valid");
let store = SavedStore::new(root, resolved).expect("saved store mapping valid");
(store, states, key, secret)
}
@@ -2637,7 +2628,7 @@ restart_timeout_seconds = 30
app
}
fn observed_runtime(app: &Application) -> ObservedRuntime {
fn observed_runtime(app: &Application) -> RuntimeObservation {
let records = app.document().peers().iter().map(|peer| {
let identity = peer.identity.expect("synthetic peer identity");
(
@@ -2650,7 +2641,7 @@ restart_timeout_seconds = 30
},
)
});
ObservedRuntime {
RuntimeObservation {
observed_at: None,
interface: InterfaceObservation::Present {
name: "wg0".to_owned(),
@@ -3590,7 +3581,7 @@ restart_timeout_seconds = 30
String::from_utf8_lossy(app.document().render())
.contains("# wg-admin:alias=bmV3IGFsaWFz")
);
assert_eq!(app.states().applied(), &RuntimeObservation::Unknown);
assert_eq!(app.states().applied(), &ApplyStatus::Unknown);
assert_eq!(
app.states().saved().get(&key).expect("saved peer").alias,
"legacy-view"
@@ -3681,7 +3672,7 @@ restart_timeout_seconds = 30
app.states().saved().get(&key).expect("saved peer").alias,
"new alias"
);
assert_eq!(app.states().applied(), &RuntimeObservation::Unknown);
assert_eq!(app.states().applied(), &ApplyStatus::Unknown);
assert!(app.states().applied_snapshot().is_some());
let backup = preview_archive(&archive).expect("pre-commit backup parses");
assert_eq!(backup.reason, BackupReason::PreCommit);
@@ -3769,11 +3760,10 @@ restart_timeout_seconds = 30
assert!(state.reserved_addresses.contains(&"10.20.0.2".to_owned()));
assert!(state.reserved_addresses.contains(&"fd20::2".to_owned()));
let reopened_states =
ClientStates::new(Snapshot::default(), None, RuntimeObservation::Unknown)
.expect("empty supported snapshot is valid");
let reopened_states = ClientStates::new(Snapshot::default(), None, ApplyStatus::Unknown)
.expect("empty supported snapshot is valid");
let reopened = Application::open(store, reopened_states).expect("session reopens");
assert_eq!(reopened.states().applied(), &RuntimeObservation::Unknown);
assert_eq!(reopened.states().applied(), &ApplyStatus::Unknown);
assert!(
reopened
.states()
@@ -3912,7 +3902,7 @@ restart_timeout_seconds = 30
app.confirm_restore(preview)
.expect("restore uses previewed bytes");
assert!(!String::from_utf8_lossy(app.document().render()).contains("draft alias"));
assert_eq!(app.states().applied(), &RuntimeObservation::Unknown);
assert_eq!(app.states().applied(), &ApplyStatus::Unknown);
assert!(app.vault().is_some());
assert!(app.states().saved().get(&key).is_some());
}
@@ -3943,7 +3933,7 @@ restart_timeout_seconds = 30
let before_restore = store.read_saved().expect("current saved bytes");
app.rename_client(key, "draft survives failed restore")
.expect("stage draft");
let applied_before = app.states().applied().clone();
let applied_before = *app.states().applied();
let preview = app
.preview_restore(selected.file_name().unwrap().to_str().unwrap())
.expect("selected archive preview");
@@ -4106,7 +4096,7 @@ restart_timeout_seconds = 30
.expect("restored reservation state parses");
assert!(state.reserved_addresses.contains(&"fd20::2".to_owned()));
assert!(app.vault().is_none());
assert_eq!(app.states().applied(), &RuntimeObservation::Unknown);
assert_eq!(app.states().applied(), &ApplyStatus::Unknown);
}
#[test]
@@ -4167,7 +4157,7 @@ restart_timeout_seconds = 30
.iter()
.all(|item| item.historical_apply == HistoricalApplyStatus::Unknown)
);
assert_eq!(app.states().applied(), &RuntimeObservation::Unknown);
assert_eq!(app.states().applied(), &ApplyStatus::Unknown);
}
#[test]
@@ -4284,7 +4274,7 @@ restart_timeout_seconds = 30
assert!(report.backup_path.is_some());
assert!(report.restart_submitted);
assert_eq!(*events.borrow(), vec!["service", "runtime", "persist"]);
assert_eq!(app.states().applied(), &RuntimeObservation::Verified);
assert_eq!(app.states().applied(), &ApplyStatus::Verified);
assert_eq!(
app.states().draft().get(&key).unwrap().alias,
"restart fixture"
@@ -4352,7 +4342,7 @@ restart_timeout_seconds = 30
);
assert_eq!(report.stage, RestartStage::Complete);
assert_eq!(app.states().applied(), &RuntimeObservation::Verified);
assert_eq!(app.states().applied(), &ApplyStatus::Verified);
assert!(app.states().applied_snapshot().is_none());
assert!(app.states().reserved_addresses().is_empty());
assert_eq!(app.saved_bytes().server_config, original_config);
@@ -4364,7 +4354,7 @@ restart_timeout_seconds = 30
let reopened = Application::open_from_saved_store(app.store.clone())
.expect("reopen historically verified saved state");
assert_eq!(reopened.historical_apply, historical_apply);
assert_eq!(reopened.states().applied(), &RuntimeObservation::Unknown);
assert_eq!(reopened.states().applied(), &ApplyStatus::Unknown);
assert!(reopened.states().applied_snapshot().is_none());
assert!(reopened.states().reserved_addresses().is_empty());
assert!(
@@ -4471,12 +4461,12 @@ restart_timeout_seconds = 30
assert_eq!(report.stage, RestartStage::Verification);
assert_eq!(report.failure, Some(RestartFailureKind::RuntimeMismatch));
assert_eq!(app.states().applied(), &RuntimeObservation::Unknown);
assert_eq!(app.states().applied(), &ApplyStatus::Unknown);
assert_eq!(app.states().reserved_addresses(), &reserved_before);
let reopened = Application::open_from_saved_store(app.store.clone())
.expect("failed verification leaves saved reservations durable");
assert_eq!(reopened.states().reserved_addresses(), &reserved_before);
assert_eq!(reopened.states().applied(), &RuntimeObservation::Unknown);
assert_eq!(reopened.states().applied(), &ApplyStatus::Unknown);
}
#[test]
@@ -4590,7 +4580,7 @@ restart_timeout_seconds = 30
assert!(!report.restart_may_still_complete);
assert!(report.manual_recovery_required);
assert!(!runtime_called.get());
assert_eq!(app.states().applied(), &RuntimeObservation::Unknown);
assert_eq!(app.states().applied(), &ApplyStatus::Unknown);
assert!(!app.states().reserved_addresses().is_empty());
}
@@ -4641,7 +4631,7 @@ restart_timeout_seconds = 30
assert!(!report.restart_may_still_complete);
assert!(report.manual_recovery_required);
assert!(!install_called.get());
assert_eq!(app.states().applied(), &RuntimeObservation::Unknown);
assert_eq!(app.states().applied(), &ApplyStatus::Unknown);
assert!(!app.states().reserved_addresses().is_empty());
}
@@ -4697,7 +4687,7 @@ restart_timeout_seconds = 30
assert!(!report.restart_may_still_complete);
assert!(report.manual_recovery_required);
assert!(!install_called.get());
assert_eq!(app.states().applied(), &RuntimeObservation::Unknown);
assert_eq!(app.states().applied(), &ApplyStatus::Unknown);
}
}
@@ -4844,7 +4834,7 @@ restart_timeout_seconds = 30
| RestartFailure::JobResultUnknown
)
);
assert_eq!(app.states().applied(), &RuntimeObservation::Unknown);
assert_eq!(app.states().applied(), &ApplyStatus::Unknown);
}
}
@@ -4865,7 +4855,7 @@ restart_timeout_seconds = 30
phase: crate::service::RestartPhase::WaitForJob,
},
|_, _| {
Ok(ObservedRuntime {
Ok(RuntimeObservation {
observed_at: None,
interface: InterfaceObservation::Missing,
})
@@ -4890,7 +4880,7 @@ restart_timeout_seconds = 30
);
assert_eq!(failed_write.stage, RestartStage::Persistence);
assert!(failed_write.manual_recovery_required);
assert_eq!(app.states().applied(), &RuntimeObservation::Unknown);
assert_eq!(app.states().applied(), &ApplyStatus::Unknown);
assert!(!app.states().reserved_addresses().is_empty());
}
@@ -4983,7 +4973,7 @@ restart_timeout_seconds = 30
verified_at_utc: verified_at.to_owned()
}
);
assert_eq!(reopened.states().applied(), &RuntimeObservation::Unknown);
assert_eq!(reopened.states().applied(), &ApplyStatus::Unknown);
}
#[test]
@@ -5021,7 +5011,7 @@ restart_timeout_seconds = 30
Err(AppError::Persistence(PersistenceError::RecoveryRequired))
));
assert_eq!(app.historical_apply, HistoricalApplyStatus::Unknown);
assert_eq!(app.states().applied(), &RuntimeObservation::Unknown);
assert_eq!(app.states().applied(), &ApplyStatus::Unknown);
assert_eq!(app.saved_bytes().internal_state, before.internal_state);
let resolved = SettingsFile::parse_toml(SETTINGS)
@@ -5036,7 +5026,7 @@ restart_timeout_seconds = 30
assert_eq!(recovered.internal_state, before.internal_state);
let reopened = Application::open(reopened_store, states).expect("application reopens");
assert_eq!(reopened.historical_apply, HistoricalApplyStatus::Unknown);
assert_eq!(reopened.states().applied(), &RuntimeObservation::Unknown);
assert_eq!(reopened.states().applied(), &ApplyStatus::Unknown);
}
#[test]
@@ -5074,7 +5064,7 @@ restart_timeout_seconds = 30
);
let reopened = Application::open(store, states).expect("committed state reopens");
assert_eq!(reopened.historical_apply, HistoricalApplyStatus::Unknown);
assert_eq!(reopened.states().applied(), &RuntimeObservation::Unknown);
assert_eq!(reopened.states().applied(), &ApplyStatus::Unknown);
}
#[test]
@@ -5127,7 +5117,7 @@ restart_timeout_seconds = 30
let pre_restore = app.confirm_restore(preview).expect("restore succeeds");
assert_eq!(app.historical_apply, HistoricalApplyStatus::Unknown);
assert_eq!(app.states().applied(), &RuntimeObservation::Unknown);
assert_eq!(app.states().applied(), &ApplyStatus::Unknown);
let archived = preview_archive(&pre_restore).expect("pre-restore backup validates");
assert_eq!(
archived.historical_apply,
@@ -5146,11 +5136,11 @@ restart_timeout_seconds = 30
let reopened_states = ClientStates::new(
snapshot_from_document(&restored_document),
None,
RuntimeObservation::Unknown,
ApplyStatus::Unknown,
)
.unwrap();
let reopened = Application::open(store, reopened_states).expect("restored state reopens");
assert_eq!(reopened.historical_apply, HistoricalApplyStatus::Unknown);
assert_eq!(reopened.states().applied(), &RuntimeObservation::Unknown);
assert_eq!(reopened.states().applied(), &ApplyStatus::Unknown);
}
}