Files
wireguard-admin/docs/architecture/domain-refactoring.md
T

51 KiB
Raw Blame History

Domain and application refactoring contract

Status: migration contract for M20–M27. This document describes the current implementation and the agreed destination boundaries; it does not authorize behavior, file-format, dependency, or product changes. M19 completes only after independent review and DOC checks.

Authority and boundaries

The server configuration is authoritative for server peers and their managed descriptive metadata. ConfigDocument preserves its bytes and exposes every peer section, even when a section has no usable public key or cannot be represented by the supported client model. ClientStates is a restricted projection used for client operations; it is not a second peer registry. The vault is authoritative for encrypted client secrets, and the saved files are authoritative for persisted state. Runtime reads are observations, not desired configuration. Historical successful verification is not current verification.

The dependency direction remains:

TUI -> application facade / application read model -> domain rules
                                      |              -> config document/editor
                                      |              -> vault/import/delivery
                                      |              -> backup/persistence
                                      |              -> runtime/service adapters

Domain rules do not read files, parse server configuration, perform cryptography, call the runtime/service, or render terminal text. The application coordinates these owners. The TUI submits typed requests and displays secret-free facts; it does not authorize business operations or parse saved configuration. The document editor never rebuilds the server file from a client snapshot.

Current concepts and migration map

References use repository-relative paths and line numbers from the M19.1 source inspection. Test names identify existing synthetic behavioral coverage; no test is claimed to have been rerun for this document.

Application session fields and workflows

Current type / field / function Business meaning Authority Proposed owner Readers / writers Existing proof Migration
Application::store (src/app.rs:81-94) Saved-file boundary, configured paths, validation and install capability SavedStore paths and installed files Application facade/session, then app/commit.rs, app/restore.rs, app/backups.rs workflow owners All app operations read; commit/restore update installed files commit_restore::backup_failure_blocks_commit_and_restore_without_losing_drafts_or_saved_bytes; persistence complete_install_publishes_components_with_private_modes M23
Application::saved Exact installed generation held by this session SavedBytes read from SavedStore Application session; saved-operation contexts in M23/M25 Open/read, commit, restore, verified bookkeeping, restart preview app::commit_advances_saved_state_without_applying_and_backup_contains_old_bytes; app::successful_restore_clears_verified_current_history_but_backup_retains_it M23, M25
Application::document Lossless current server-configuration draft and peer authority Original/current saved server bytes parsed as ConfigDocument app/draft.rs draft owner (M22), with editing rules in wireguard/edit.rs Application reads; create/rename/delete/restore replace it; Commit serializes it app::new_client_preparation_is_transactional_and_uncommitted_keys_cannot_issue; config_preservation::edits_keep_interface_and_unowned_bytes_exact_and_can_be_reparsed M22
Application::states Narrow draft/saved management snapshots, apply knowledge, reservations Document projection for manageable clients plus persisted internal state Domain ClientStates, optionally domain/apply.rs and domain/reservations.rs App edits/Commit/restart/reopen; app and TUI currently read Domain tests discard_restores_draft_and_commit_keeps_applied_snapshot_separate, successful_verified_apply_releases_deleted_addresses_and_mismatch_does_not; persistence::persisted_history_never_marks_the_runtime_verified M20, M21, M22
Application::vault Encrypted vault draft used for secret encryption, import and reissue Installed vault bytes at open, then current app draft app/draft.rs coordinates; cryptographic/value operations stay in vault.rs App setup/import/create/delete/Commit/reissue read or update it app::selected_file_import_uses_the_existing_vault_draft_without_saving; app::deletion_only_removes_selected_current_record_and_retains_other_records; vault::encrypted_bundle_round_trips_without_unlocking_and_retrieves_with_password M22, M24
draft_unsupported_reservations Draft-only exact host addresses retained when a saved peer cannot project to Client Saved document peer routes and pending deletion Domain reservation policy, staged by application draft owner Delete adds; Commit persists/promotes; discard clears app::deletion_of_unprojectable_peer_persists_its_exact_host_route_reservation; app::deletion_of_unprojectable_peer_with_broad_route_fails_without_draft_change M21, M22
historical_apply Last successfully persisted verification timestamp/status, for archive/history display PersistedState.last_verified_at_utc and archive manifest Persistence codec owns representation; application read model presents it Open/backup/restore/restart bookkeeping persistence::persisted_history_never_marks_the_runtime_verified; app::verified_history_persists_across_reopen_without_marking_runtime_applied; app::historical_verified_timestamp_does_not_suppress_reissue_warning_after_reopen M21, M23, M26
verified_saved_projection Saved generation verified by the current session, used for delivery activation warning Successful full runtime comparison bound to saved bytes Application session/restart scenario; read-only view exposes only safe status Restart publication writes; delivery warning reads; edits/restore invalidate as appropriate app::first_issue_warning_tracks_verified_saved_generation_for_file_and_qr; app::historical_verified_timestamp_does_not_suppress_reissue_warning_after_reopen M21, M24, M25, M26
active_restart_attempt, next_restart_attempt One-use authorization that binds verification publication to the confirmed attempt and saved generation Application-created attempt ID plus saved-byte equality Private app/restart.rs scenario context Preview/confirm/restart lifecycle; verified persistence consumes it app::restart_confirmation_rejects_a_generation_changed_after_preview; app::restart_mismatch_and_post_restart_write_failure_never_release_addresses M20, M25
initial_issues, next_issue_authorization Creation-session-only first-delivery eligibility, one-use authorization and retry state Generated key held in memory, committed peer directives and encrypted record binding app/delivery.rs session owner; vault.rs remains cryptographic authority Create registers after coordinated draft staging; Commit promotes; delivery consumes or updates; delete/discard/invalidation removes app::first_issue_authorization_is_single_flight_and_success_consumes_it; app::deletion_and_discard_revoke_only_session_bound_initial_material; app::uncertain_first_file_publication_retains_warning_and_skips_qr M20, M22, M24
open, open_from_saved_store, open_saved (src/app.rs:493-545) Construct a session from supplied state or derive supported state from saved document; reopen is Unknown now, regardless of history Installed files/document; persisted reservations/history Application session constructor Production startup uses open_from_saved_store; open is used by tests startup::management_session_loads_supported_state_and_keeps_unsupported_peers_visible; app::verified_history_persists_across_reopen_without_marking_runtime_applied M22/M27 compatibility review
prepare_new_client, add_client, rename_client, delete_client, discard_draft (src/app.rs:603-705,1253-1385) Stage all client/document/vault changes without saved-file or runtime effects Document plus restricted client projection and vault app/draft.rs and cohesive app/clients.rs operations, calling domain/editor/vault owners UI uses prepare/rename/delete/discard commands; container test driver uses prepared flow; add_client has test callers only app::new_client_preparation_is_transactional_and_uncommitted_keys_cannot_issue; client_delivery::committed_creation_retries_after_overwrite_decline_and_duplicate_aliases_are_independent; client_delivery::deletion_and_restore_remove_password_free_authority_and_reopen_requires_vault_password M22
commit, preview_restore, confirm_restore (src/app.rs:1853-1965,2041-2147) Commit atomically installs staged saved bytes after backup; restore is a preview-bound full saved-state replacement after backup App draft for Commit; validated archive payload for restore app/commit.rs, app/restore.rs; low-level formats/install remain in backup.rs/persistence.rs TUI executor calls facade; store installs only with BackupComplete app::commit_advances_saved_state_without_applying_and_backup_contains_old_bytes; app::preview_is_read_only_and_confirm_restores_without_unlocking_vault; commit_restore::backup_failure_blocks_commit_and_restore_without_losing_drafts_or_saved_bytes M23
preview_restart, restart, mark_verified_with_attempt (src/app.rs:1422-1530,1967-2038) Confirmed restart scenario: preconditions, backup, service job completion, runtime comparison, durable status then publish applied knowledge Saved generation + complete SavedPeerProjection + runtime observation app/restart.rs; service submission stays service.rs; comparison stays runtime.rs; durable fields stay persistence TUI previews/confirms; fixture callback is test-only; success updates state only after durable write app::confirmed_restart_orders_backup_job_read_and_durable_publication; app::service_timeout_reports_submission_and_may_still_complete; app::failed_verified_history_write_does_not_advance_memory_or_reopen_as_verified M20, M21, M25

Domain state and transitions

Current type / field / operation Business meaning Authority Proposed owner Readers / writers Existing proof Migration
Client / ClientAddresses / Snapshot (src/domain.rs:60-147) Public, manageable projection keyed by public key; aliases may repeat, IPv6 may be absent for legacy clients Public key and supported host-address projection from document Domain client/value modules App operations and read model; draft/saved snapshot transitions domain::aliases_may_repeat_but_control_characters_are_rejected; domain::existing_ipv4_only_clients_remain_ipv4_only; wireguard::wireguard_projection_supports_ipv4_only_and_rejects_ipv6_only_or_default_routes M20/M21 mechanical module move only if useful
ClientStates::draft / saved (src/domain.rs:159-165) Manageable-client snapshots for staged and committed state, not all server peers Document is source; snapshots are derived Domain client states, mutated only through narrow transitions Application edits draft, Commit advances saved; TUI currently reads both domain::discard_restores_draft_and_commit_keeps_applied_snapshot_separate; app::commit_advances_saved_state_without_applying_and_backup_contains_old_bytes M21/M22
applied_snapshot / domain RuntimeObservation Optional management snapshot last verified plus current knowledge enum; may be None while full technical verification is Verified for valid unmanageable peers Current verified runtime comparison; historical timestamp is separate Domain apply knowledge; technical evidence remains runtime-owned Restart publication changes; reopen resets current knowledge; read model maps current knowledge app::restart_verifies_metadata_free_legacy_routes_without_partial_applied_snapshot; app::verified_history_persists_across_reopen_without_marking_runtime_applied M20 naming; M21 transitions
reserved_addresses and restore_persisted_reservations (src/domain.rs:164,210-231) Persisted conservative addresses that cannot be allocated Prior saved peer routes and internal state domain/reservations.rs value/rules; persistence.rs only encodes/decodes Allocator reads; Commit/reopen/restart update; restart success may release domain::a_draft_only_deletion_does_not_create_a_committed_reservation; domain::successful_verified_apply_releases_deleted_addresses_and_mismatch_does_not; persistence::state_codec_is_canonical_and_rejects_bad_or_unsupported_state M21
draft_mut / commit_draft / discard_draft (src/domain.rs:233-256) Current unrestricted draft mutation and snapshot transitions ClientStates snapshots, ultimately document-derived Replace with narrow domain operations in M21/M22; no public unrestricted mutator Production draft_mut callers are only inside Application; direct outside-app callers are colocated domain tests Domain tests above; app tests discard_restores_draft_without_writing_installed_bytes, commit_failure_keeps_pending_issue_and_successful_retry_authorizes_it M21/M22; visibility removal after caller migration
mark_applied_after_verification, mark_technically_applied_after_verification, runtime unknown/unavailable (src/domain.rs:258-296) Change present apply knowledge only after verified result publication; preserve current uncertainty and optional management projection Runtime comparison result plus successful persisted bookkeeping Domain apply transitions called only by application restart scenario App restart success/failure/reopen; periodic TUI statistics must not call them domain::successful_verified_apply_releases_deleted_addresses_and_mismatch_does_not; app::restart_mismatch_and_post_restart_write_failure_never_release_addresses; tui::statistics_refresh_is_generation_bound_and_never_changes_applied_state M20/M21
allocate_ipv4, allocate_ipv6, AddressAvailability (src/domain.rs:341-464) Pure address allocation against server, occupied and reserved sets Validated settings and document-derived address sets Domain address allocator/reservations App create reads domain::ipv4_allocation_skips_server_occupied_reserved_network_and_broadcast; domain::ipv6_allocates_first_free_address_from_a_very_large_range; tui_workflow::create_form_submits_automatic_dual_stack_client_without_changing_saved_state M21

Peer and runtime projections

Current projection Business meaning / authority Proposed owner and consumers Existing proof Migration
PeerInspection, PeerIssue, DocumentIssue, ConfigDocument (src/wireguard/document.rs:104-176) Every authoritative server peer section, byte spans, parsed routes and anomalies. Identity can be absent or duplicated. Malformed/no-key peers remain inspectable by document position. wireguard/document.rs; app editing/deletion/restart and application read model consume it wireguard::wireguard_peer_issues_report_missing_malformed_and_duplicate_identity_fields; tui::malformed_peer_can_be_inspected_by_document_location_without_mutation_identity; tui::duplicate_public_keys_remain_inspectable_by_peer_position_but_not_mutable M22/M25/M26
ClientDisplay / SessionDisplay (src/app.rs:39-77,559-600) Secret-free current draft-document rows; mutable_identity only for one supported and unique projected client; metadata alias is descriptive, not independent authority Move fact construction into app-owned app/read_model.rs; renderer consumes it tui::enter_opens_an_unsupported_peer_for_inspection_without_mutation_identity; tui::issues_screen_hides_valid_address_shape_limits_but_keeps_genuine_errors M26
SessionView::from_application, ClientStateRow, VisibleRecord (src/tui/state.rs:44-165) Current duplicate app projection plus UI generation, saved peer count, state map, reservation facts and visible removed rows Split: application owns public secret-free view and action eligibility; TUI retains selection, screen, scroll and rendering state tui_workflow::saved_peer_projection_counts_legacy_peers_without_inventing_managed_state; tui::keyboard_navigation_reaches_draft_removed_rows_without_authorizing_them; tui::refresh_preserves_selection_by_public_key_not_alias_or_row_position M26
Renderer-derived state and issue text (src/tui/render.rs:1198-1265,1759-1835) Display labels/counts assembled from projection facts; currently some business categories are inferred in UI Move counts/classification to read model only where authoritative facts permit; keep styling, wrapping, geometry and final strings/rendering TUI-owned tui::persistent_details_follow_the_highlight_and_keep_geometry_fixed; tui::client_rows_show_draft_state_and_escape_anomalous_unsupported_peers; tui::issues_screen_hides_valid_address_shape_limits_but_keeps_genuine_errors M26
SavedPeerProjection, RuntimeObservation, TechnicalMatch (src/runtime.rs:19-218) Complete technical route set independent of manageable Client; valid subnet-route peers can be restart-verified without UI mutability. Handshakes/counters are not apply evidence. Runtime owns projection/comparison and read-only transport; app restart scenario coordinates; read model may expose only status/findings needed for UI runtime::projection_keeps_valid_unsupported_routes_and_ipv4_only_peers; runtime::counters_and_handshake_changes_do_not_change_technical_match; app::restart_verifies_metadata_free_legacy_routes_without_partial_applied_snapshot M20 name clarification; M25/M26

Authorization, prepared contexts and worker generations

Current context Meaning and authority Proposed owner Readers / writers Existing proof Migration
UI selection guard UiState::selected_client_mutable (src/tui/state.rs:473-491) Presentation-level guard requires one matching row, matching document position if selected, and a mutable identity; cannot grant operation authority TUI selection state only; application owns typed per-action eligibility and execution revalidation Terminal key handlers and footer hints read it; app methods/editors independently reject invalid actions tui::selection_uses_peer_position_for_duplicate_keys_and_disables_mutation; tui::removed_saved_client_is_visible_and_never_mutable M26
Current rename/delete/delivery UI prerequisites (src/tui/terminal.rs:771-823) Current TUI rename/delete guards both require a supported unique mutable identity; delivery/reissue additionally require saved membership. Separately, Application::delete_client supports deletion by public key for some exact-host-route peers outside ClientStates. M26 must audit this mismatch against the planned per-action eligibility contract; do not infer that current keyboard UI exposes that app-level capability or silently prohibit the documented supported path. Application reports typed per-action eligibility, then each application operation revalidates its own preconditions; terminal maps eligibility to enabled actions Terminal selects target; ConfigDocument/app authoritatively validate at execution tui::detail_hints_only_offer_saved_delivery_for_mutable_saved_selection; app::deletion_of_unprojectable_peer_persists_its_exact_host_route_reservation; app::deletion_of_unprojectable_peer_with_broad_route_fails_without_draft_change M26
InitialIssue, IssueContext, FirstIssueAuthorization, IssueStage (src/app.rs:96-126,976-1219) Session-local secret and one-use authorization bound to identity, peer directives and encrypted record; retries reflect publication/cleanup uncertainty app/delivery.rs owns session lifecycle; vault owns ciphertext/crypto; file adapter owns publication mechanics Create registers; Commit enables; each delivery validates/consumes; edits/delete/discard invalidate as appropriate app::committed_first_issue_publishes_selected_file_and_consumes_eligibility; app::published_file_is_reported_when_saved_store_bookkeeping_fails; app::failed_first_delivery_requires_consuming_capability_before_retry; app::temporary_cleanup_failure_requires_manual_cleanup_before_first_issue_retry M20/M24
Application::vault_draft_mut (src/app.rs:1310-1314) Public unrestricted mutable vault escape hatch Remove or narrow after callers migrate; vault logic stays in vault/app draft owners Only current caller: colocated app test replacing_the_encrypted_record_invalidates_committed_first_issue (~3525); no production, startup, integration or container-driver caller Same test plus app::deletion_and_discard_revoke_only_session_bound_initial_material M24/M27; replace test setup intentionally before visibility removal
ApplicationExecutor::prepared_destination, restore_preview (src/tui/events.rs:290-301) Worker-owned continuation state bound to target, delivery mode/QR choice, attempt, preview and request generation; prepared-destination tuple currently has six elements and restore-preview tuple three Private named contexts in tui/events.rs or a cohesive private executor child ApplicationExecutor::execute_at and execute_sensitive_at; clears on unrelated actions, consumes on confirmation/cancel events::prepared_delivery_and_restore_confirmations_are_bound_to_the_reviewed_generation; client_delivery::overwrite_confirmation_is_bound_to_the_selected_destination; TUI workflow restore cancellation/confirmation M20
PendingOperation, SessionView.generation, worker generation (src/tui/events.rs:168-189,755-888; src/tui/state.rs:52-66,226-235) Monotonic identity for submitted work and returned view; stale view/statistics cannot overwrite current UI state TUI worker/execution adapter only; not domain state Mutating submit increments; worker tags result; UI accepts matching generations; periodic statistics rechecks generation across read events::delayed_statistics_are_bounded_and_do_not_advance_generation; events::statistics_started_before_a_mutation_are_discarded_as_stale; tui::stale_view_is_rejected_and_unsupported_selected_peer_is_not_mutable M20/M26
FormController, SecretInput, SecretText, SensitiveCommand (src/tui/forms.rs:50-145; src/tui/events.rs:78-100) Short-lived form state and zeroizing password command; secrets excluded from view/debug/clone Forms own input; worker carries secret command; application consumes only during vault operation Form submits sensitive command; worker invokes setup/reissue; form clears on cancel/consumption tui::compact_form_hints_follow_the_active_form_without_leaking_input; vault::every_retrieval_requires_the_correct_password; client_delivery::reissue_unlocks_installed_saved_record_each_time_and_warns_after_reopen M24/M26; preserve lifetimes

Public API escape-hatch callers and removal conditions

API Actual callers Safe path and timing
ClientStates::draft_mut (src/domain.rs:233) Production: Application methods only (app.rs:611,680,1259,1301). Other direct callers: domain.rs unit tests. No lib.rs, startup, TUI, integration-test or container-driver consumer. Replace application uses with narrow stage/rename/remove operations as M21/M22 move transitions. Update colocated tests to exercise those operations. Reduce visibility only after the complete source and test search finds no external caller.
Application::vault_draft_mut (src/app.rs:1312) Only app unit test replacing_the_encrypted_record_invalidates_committed_first_issue; no production or integration caller. Replace test mutation with a purpose-built test fixture or behavior through a legitimate application operation; do not retain a production mutation bypass for tests. Remove/narrow in M24 or M27 after verification.
Application::add_client (src/app.rs:605) No production caller. Integration: tests/commit_restore.rs at archive-cycle, reservation and backup-failure setup; tests/runtime_restart.rs fixture helper; app unit restart setup. Distinct ConfigDocument::add_client remains used by application/editor and preservation tests. Preserve app facade operations through M22, migrate fixture uses to deliberate setup or create workflow while keeping assertions, then remove only if full caller audit confirms no external library consumer. M27 owns final pruning.
Application::open(store, states) (src/app.rs:493) App unit tests and integration fixture constructors in client_delivery.rs, commit_restore.rs, runtime_restart.rs, tui_workflow.rs. No production startup caller. Keep compatibility while tests depend on seeded state. Migrate tests that need no injected state to open_from_saved_store; tests that specifically validate state seeding may retain a justified constructor. Remove only after binary, library, integration and test-only container callers are checked in M27.
Application::open_from_saved_store (src/app.rs:501) Production: startup::open_management_session (src/startup.rs:65-92). Also application/event/TUI tests and container_driver::open_fixture_application. container_driver itself is library #[cfg(test)] only (src/lib.rs:14-17). Required production entry point; retain or provide an equivalent public facade. Fixture driver has no production fixture-apply capability.

Transition tables

These tables preserve observed behavior. “No mutation” below means no protected saved-file or runtime mutation; drafts may remain in memory where specified.

Draft lifecycle

Trigger Preconditions Next state Side effects Failure or cancellation Existing proof
Open session Saved settings/config validate; optional state/vault parse Draft begins from saved document and client snapshot; current apply knowledge is Unknown Read installed bytes, restore conservative reservations, load history separately Invalid saved state rejects open; history never promotes current apply knowledge startup::management_session_loads_supported_state_and_keeps_unsupported_peers_visible; app::verified_history_persists_across_reopen_without_marking_runtime_applied
Create client Vault exists; alias/address inputs valid; configured ranges available Document/client/vault drafts all contain new peer/record; first-issue material registers only after staging succeeds Generate key, allocate/check dual stack, encrypt record, write no files Any preparation failure leaves all live draft components and issue rights unchanged app::new_client_preparation_is_transactional_and_uncommitted_keys_cannot_issue; app::manual_addresses_reject_occupied_and_out_of_range_without_partial_staging
Rename or delete Target identity resolves to an authoritative unambiguous peer; deletion of unprojectable peer can reserve only exact host routes Corresponding document and manageable snapshot edit; delete removes its vault record if present Mutate in-memory drafts; retain exact unsupported routes pending Commit; invalidate relevant issue eligibility Malformed/ambiguous/unsafe broad-route edit fails without partial draft change app::deletion_of_unprojectable_peer_with_broad_route_fails_without_draft_change; app::deletion_only_removes_selected_current_record_and_retains_other_records
Discard draft Session can reread saved bytes Draft returns to installed saved state Reparse installed document/vault, restore persisted reservations; retain only still-valid awaiting-first-file issue material Read/parse failure returns error; no installed writes; already-consumed delivery is not recreated app::discard_restores_draft_without_writing_installed_bytes; app::deletion_and_discard_revoke_only_session_bound_initial_material
Commit Draft validates; mandatory pre-Commit archive succeeds Saved state advances to installed draft only after successful recoverable install Persist document, encrypted vault and internal reservations; no runtime call; apply knowledge becomes unknown when saved differs Backup failure stops before protected writes; partial install reports failure/recovery; draft remains retryable app::backup_failure_preserves_installed_files_and_draft; app::injected_archive_and_install_failures_preserve_bytes_and_session_saved_state; app::commit_advances_saved_state_without_applying_and_backup_contains_old_bytes
Restart with draft Valid saved-state restart preview; explicit choice Cancel keeps draft; discard choice resets it before restart sequence Separate restart flow; Commit never applies Cancel makes no backup/job/runtime call. Once service request is submitted, cancellation/timeout cannot assert that the job did not finish app::restart_cancel_preserves_draft_and_never_calls_adapters; app::service_timeout_reports_submission_and_may_still_complete

Address reservations

Trigger Preconditions Next state Side effects Failure or cancellation Existing proof
Delete saved manageable client in draft Target's addresses known No committed reservation yet; addresses remain occupied by saved snapshot Draft deletion only Discard restores client and no pending reservation is published domain::a_draft_only_deletion_does_not_create_a_committed_reservation
Delete saved unprojectable peer in draft Every route to be retained is an exact IPv4 /32 or IPv6 /128 host route Exact routes enter draft-only reservation set Prevent allocation while deletion is staged Broad/subnet route or missing address evidence rejects deletion without draft mutation app::deletion_of_unprojectable_peer_persists_its_exact_host_route_reservation; app::deletion_of_unprojectable_peer_with_broad_route_fails_without_draft_change
Commit deletion Mandatory backup and saved install succeed Deleted saved addresses enter persisted reservation set; committed client snapshot advances Allocator excludes current draft/saved and reserved addresses Backup/install failure does not publish release or new saved state domain::discard_restores_draft_and_commit_keeps_applied_snapshot_separate; app::state_file_round_trip_keeps_pending_addresses
Restore archive removing current peers Preview is valid and confirmation accepted; pre-restore backup succeeds Rebuilt internal state carries existing persisted reservations and addresses from ClientStates::saved(), except addresses assigned by restored manageable clients. preview_restore does not collect exact host routes from unprojectable peers in the installed saved server configuration or draft_unsupported_reservations; their preservation is not established. Full saved replacement only; runtime is untouched Cancel leaves saved state unchanged; backup/install failure leaves recoverable prior state and drafts commit_restore::restore_reserves_addresses_of_current_saved_clients_that_the_archive_removes; app::partial_restore_failure_preserves_session_and_pre_restore_backup_then_reopens_to_old_set
Verified restart publication Confirmed attempt still matches installed generation; completed service job; complete interface/peer match; internal-state install succeeds Reservations for removed clients clear Persist verified timestamp/state, then publish current applied knowledge Mismatch/read/service/timeout/persistence failure retains reservations and conservative apply knowledge domain::successful_verified_apply_releases_deleted_addresses_and_mismatch_does_not; app::restart_mismatch_and_post_restart_write_failure_never_release_addresses

Current and historical verification

Trigger Preconditions Next state Side effects Failure or cancellation Existing proof
Open/reopen Saved configuration and internal state parse Current state Unknown; optional management applied snapshot absent; historical timestamp remains separately available Restore reservations from persisted metadata only Invalid internal state rejects session; historical success is not live proof persistence::persisted_history_never_marks_the_runtime_verified; app::verified_history_persists_across_reopen_without_marking_runtime_applied
Periodic statistics read Worker has an interface name; bounded read returns observation UI statistics update only No domain apply transition, no saved write, no reservation release Unavailable/unknown data remains so; stale-generation result is discarded tui::events::statistics_started_before_a_mutation_are_discarded_as_stale; tui::state::statistics_refresh_is_generation_bound_and_never_changes_applied_state
Saved state changes Commit/restore changes installed generation Current apply knowledge becomes Unknown; old historical timestamp may remain or be cleared according to existing successful restore bookkeeping No runtime operation Failed write leaves in-memory published state conservative app::commit_advances_saved_state_without_applying_and_backup_contains_old_bytes; app::successful_restore_clears_verified_current_history_but_backup_retains_it
Restart comparison matches complete saved technical peer set Interface identity and complete peer dump match; malformed/duplicate/unverifiable records absent Still not published until durable verification bookkeeping succeeds Prepare persisted reservations/timestamp update Persistence failure leaves in-memory apply knowledge/reservations unchanged app::confirmed_restart_orders_backup_job_read_and_durable_publication; app::failed_verified_history_write_does_not_advance_memory_or_reopen_as_verified
Full technical match with peer outside management projection Peer record is valid/verifiable even if not a Client, e.g. subnet routes Knowledge may be Verified with no complete applied_snapshot Persist verified result; do not invent partial management snapshot Any incomplete runtime dump still cannot pass app::restart_verifies_metadata_free_legacy_routes_without_partial_applied_snapshot; runtime::projection_keeps_valid_unsupported_routes_and_ipv4_only_peers

First delivery and password reissue

Trigger / stage Preconditions Next state Side effects Failure or cancellation Existing proof
Create before Commit Successful coordinated create in this session Pending draft issue Hold generated private key in session and bind peer directives/ciphertext Any failed create publishes no new issue authorization app::new_client_preparation_is_transactional_and_uncommitted_keys_cannot_issue
Commit succeeds Created peer and encrypted record appear in installed state and match session context Awaiting first file Promote one-use capability for current session Commit failure keeps retryable draft capability but does not permit output app::commit_failure_keeps_pending_issue_and_successful_retry_authorizes_it
Prepare destination Eligible committed issue or saved reissue selected; output path validated Prepared destination is bound to identity, mode, QR request, attempt and generation No key disclosure or file write yet Unrelated command/cancel invalidates the prepared context; stale generation requires reselection events::prepared_delivery_and_restore_confirmations_are_bound_to_the_reviewed_generation; client_delivery::overwrite_confirmation_is_bound_to_the_selected_destination
Confirm first file Same session, context still matches saved record, overwrite confirmation if required File is published; first issue is consumed only after saved bookkeeping Secure file publication; optional QR derives from the same payload and happens after mandatory file Before-publication failure can retry; uncertain publication requires overwrite confirmation; cleanup failure requires manual cleanup; QR failure does not undo file app::failed_file_publication_can_retry_in_a_different_directory; app::uncertain_first_file_publication_retains_warning_and_skips_qr; app::temporary_cleanup_failure_requires_manual_cleanup_before_first_issue_retry; app::file_and_qr_share_payload_and_qr_failure_still_consumes_first_issue
Saved bookkeeping after file File publication succeeded Consumed if bookkeeping succeeds; otherwise explicit published-but-uncertain result, no automatic retry capability Report file exists; preserve activation warning Do not misreport as failed/unpublished or auto-delete file app::published_file_is_reported_when_saved_store_bookkeeping_fails
Reissue after session/reopen Selected client is in saved state; password supplied for this action No first-issue session eligibility is involved Unlock matching saved vault record, render/write selected client file; optional QR Wrong password/record mismatch/file failure is reported without secret leakage; each client requires fresh password client_delivery::reissue_unlocks_installed_saved_record_each_time_and_warns_after_reopen; vault::every_retrieval_requires_the_correct_password; client_delivery::imported_ipv4_only_parameters_are_preserved_and_authenticated_conflict_blocks_reissue

Restore preview and confirmation

Trigger Preconditions Next state Side effects Failure or cancellation Existing proof
Select archive for preview Safe archive name and valid archive format Opaque preview holds validated bytes plus metadata; UI receives redacted summary/change list and generation Read-only archive validation; no password and no runtime call Invalid/traversal archive rejected; cancel drops preview app::preview_is_read_only_and_confirm_restores_without_unlocking_vault; commit_restore::archive_parser_rejects_traversal_links_duplicates_and_oversized_members
Confirm matching preview Preview token still belongs to the operation generation; installation paths remain compatible Selected archive replaces saved settings, server configuration and vault as a whole after mandatory pre-restore backup; running VPN remains unchanged Preview rebuilds internal state instead of preserving its archive bytes: retain archived reservations and current reservations, add addresses of currently saved clients removed by the archive, then exclude addresses assigned by restored clients. The rebuilt state has no historical verification timestamp; current apply knowledge becomes Unknown. No restart occurs. The pre-restore backup retains the prior saved bytes and history. Stale/cancelled preview cannot write; backup/install failure preserves prior saved state or requires journal recovery events::prepared_delivery_and_restore_confirmations_are_bound_to_the_reviewed_generation; app::restore_accepts_changed_non_path_settings_and_rejects_changed_paths; commit_restore::restore_reserves_addresses_of_current_saved_clients_that_the_archive_removes; app::successful_restore_clears_verified_current_history_but_backup_retains_it; commit_restore::backup_failure_blocks_commit_and_restore_without_losing_drafts_or_saved_bytes
Cancel preview/confirmation No restore install has begun Existing saved state and draft remain Clear app-executor preview context No backup/install/runtime side effect tui_workflow::worker_backup_restore_preview_cancel_and_confirm_are_separate_saved_operations; app preview read-only test above

Restart attempt identity and uncertainty

Trigger Preconditions Next state Side effects Failure or cancellation Existing proof
Preview restart Installed bytes equal session saved generation; interface/service settings and complete saved peer projection are safe Read-only preview captures saved bytes/settings/projection and draft presence No backup/job/runtime operation Unsafe or ambiguous peer state blocks before backup; stale saved bytes reject confirmation app::unsafe_saved_peer_diagnostics_block_preview_before_backup_with_safe_location; app::restart_confirmation_rejects_a_generation_changed_after_preview
Cancel choice Preview still valid Cancelled report; draft remains No backup, service request, runtime read, or persistence No operation was submitted app::restart_cancel_preserves_draft_and_never_calls_adapters
Confirm restart Preview still current; explicit choice; backup succeeds Attempt identity bound to saved generation, then service job submitted Optional draft discard at specified safe point; complete backup; one shared deadline Backup failure submits no job; before submission cancellation is definite app::restart_backup_failure_preserves_draft_and_submits_no_job; app::confirmed_restart_orders_backup_job_read_and_durable_publication
Service job submitted Request accepted Await job completion under same deadline Job may still complete even if caller times out/disconnects Timeout report retains submitted/may-still-complete distinction; never claim cancellation or rollback app::service_timeout_reports_submission_and_may_still_complete; app::submitted_terminal_job_failures_cannot_still_complete
Job complete, observe and compare Job successful; remaining shared deadline; expected configured interface and complete runtime peer set Matching result becomes candidate for durable verification Bounded observation and pure comparison; no direct state publish yet Read error/timeout/missing interface/incomplete dump/mismatch stops publication and retains reservations app::shared_deadline_expired_after_successful_job_stops_before_runtime_read; app::shared_deadline_expired_after_runtime_read_reports_timeout_before_comparison; runtime::partial_duplicate_and_unavailable_records_never_pass
Durable verification succeeds Attempt token and both current/attempt saved bytes still equal; full technical match; internal state install succeeds Publish current verified knowledge and clear retained addresses Persist timestamp/reservations first; update in-memory session after successful install Failed persistence leaves conservative state; no second restart or automatic rollback app::failed_verified_history_write_does_not_advance_memory_or_reopen_as_verified; app::restart_mismatch_and_post_restart_write_failure_never_release_addresses

Destination modules and staged migration

These destinations are bounded homes for current responsibilities, not a request to create empty files. Keep established public facade calls until callers have migrated. Existing low-level adapters and persisted formats remain in place.

Milestone Production surface and destination Compatibility / preserved boundary Existing baseline and exact focused commands
M20 — vocabulary and named contexts domain.rs apply-knowledge naming; runtime.rs measured observations; tui/state.rs presentation mapping; tui/events.rs private named prepared-delivery and restore contexts. Split coherent domain children only if useful. No business-rule movement, new type semantics, schema change, secret Clone/Debug, or command redesign. Replace the six-element prepared-destination tuple with named fields for identity, QR choice, delivery mode, retry attempt, generation and destination. Keep public imports via re-export only when actual callers need them. cargo test --locked domain::; cargo test --locked runtime::; cargo test --locked tui::events::; cargo test --locked tui::state::. Proof names: verified_state_requires_a_matching_applied_snapshot, counters_and_handshake_changes_do_not_change_technical_match, prepared_delivery_and_restore_confirmations_are_bound_to_the_reviewed_generation.
M21 — reservations and apply knowledge domain/reservations.rs and domain/apply.rs as justified; allocator remains domain; persistence.rs retains TOML serialization; runtime.rs retains technical matching; app restart caller migrates to transitions. Preserve PersistedState version/field names and semantics, exact host-route retention, Unknown on reopen, optional incomplete management snapshot, and no public unrestricted mark-applied API. cargo test --locked domain::; cargo test --locked persistence::; cargo test --locked app::. Proof names: reservation and verified-release domain tests; persisted_history_never_marks_the_runtime_verified; restart_mismatch_and_post_restart_write_failure_never_release_addresses.
M22 — coordinated draft and create/edit/import app/draft.rs coordinates document, client projection, encrypted vault and draft-only reservations; cohesive app/clients.rs and app/import.rs only if responsibility warrants. Keep Application facade methods. Domain mutators become narrow; vault/source.rs still reads sources and vault/import.rs reconciles/encrypts. Prepare fallible replacements before publication; document remains authoritative; no file/runtime writes; no partially staged issue capability. Preserve original files, anomaly reports, metadata and unsupported peers. Retain app facade entry points while startup/tests migrate. cargo test --locked app::; cargo test --locked --test config_preservation; cargo test --locked --test client_delivery; cargo test --locked --test vault_import; cargo test --locked --test tui_workflow. Existing coverage: app create/import/deletion tests and initial_setup_encrypts_valid_files_and_leaves_sources_and_server_unchanged.
M23 — saved Commit, backups and restore app/commit.rs, app/restore.rs, app/backups.rs own ordered scenarios; persistence.rs keeps journaling/path safety and backup.rs keeps archive encoding/parsing. No persisted format, archive member/version, settings-path policy, rollback, runtime or service behavior changes. Keep preview byte-bound and saved-only. cargo test --locked app::; cargo test --locked persistence::; cargo test --locked backup::; cargo test --locked --test commit_restore; cargo test --locked --test client_delivery. Proof includes commit/install failure and restore preview/cancel/backup tests.
M24 — delivery scenarios app/delivery.rs owns first-issue session eligibility, retry and password reissue orchestration; delivery.rs, delivery/files.rs, delivery/qr.rs, vault crypto stay focused adapters. Preserve one-use/session-only secret capability, prepared destination ownership, same payload for file/QR, per-client password and publication uncertainty/manual cleanup. cargo test --locked app::; cargo test --locked delivery::; cargo test --locked vault::; cargo test --locked --test client_delivery; cargo test --locked --test tui_workflow.
M25 — restart scenario app/restart.rs owns preview, attempt token, backup/job/read/compare/durable-publication order. runtime.rs owns complete peer projection/comparison; service.rs owns systemd adapter; persistence owns durable state. Keep test-only fixture apply behind #[cfg(test)]. Preserve shared deadline, generation/attempt checks, job-submission uncertainty, unsupported-but-verifiable peers, and reservations until durable success. No real host service calls during tests. cargo test --locked app::; cargo test --locked runtime::; cargo test --locked service::; cargo test --locked --test commit_restore; cargo test --locked --test runtime_restart.
M26 — read model and thin TUI command adapter app/read_model.rs owns secret-free peer/state/issue/reservation/apply facts and per-action eligibility. tui/state.rs retains UI navigation/generation; events/executor owns worker lifecycle and command adaptation; forms own transient inputs; renderers own visual strings/styles/layout. No TUI parsing of saved bytes or duplicated authorization policy. Preserve malformed-row inspection by document position, draft removals, action-specific eligibility, QR disclosure, keyboard behavior, worker serialization/generations/cancellation/shutdown and layout. cargo test --locked tui::; cargo test --locked --test tui_workflow; cargo test --locked --test client_delivery. Exact worker, view-generation, selection and PTY tests are listed above.
M27 — integrated cleanup and handoff Reconcile actual final owners in source, docs/PROJECT_LAYOUT.md, glossary and a reading guide; remove obsolete forwarding APIs only after all callers have migrated. No feature addition, format/dependency changes, assertion weakening, or real-system claim. Review library/binary/integration/test-only container callers and M8 evidence invalidation. cargo test --locked --test config_preservation; cargo test --locked --test commit_restore; cargo test --locked --test client_delivery; cargo test --locked --test runtime_restart; cargo test --locked --test vault_import; cargo test --locked --test tui_workflow; plus required RUST gates.

Test inventory gaps and verification notes

  • There is no tests/client_lifecycle.rs target. Lifecycle coverage is distributed across app::, client_delivery, commit_restore, vault_import, and tui_workflow; the planned M22–M26 commands use existing targets, and a new target is needed only if acceptance finds a real behavioral gap.
  • There is no dedicated current integration test named for application projection/action eligibility. Existing checks are spread across app:: display logic, tui::state, tui::render, and tui_workflow::saved_peer_projection_counts_legacy_peers_without_inventing_managed_state.
  • Restore reservation coverage currently proves only that addresses from manageable clients in ClientStates::saved() survive replacement (commit_restore::restore_reserves_addresses_of_current_saved_clients_that_the_archive_removes). Application::preview_restore unions persisted reservations with those manageable saved-client addresses; it does not read exact host routes from unprojectable peers in the installed saved server configuration or include draft_unsupported_reservations. The existing policy keeps deleted-client addresses reserved until verified restart, so this is a potential correctness gap, not an agreed exception. M23 should add focused restore coverage for these sources and repair any uncovered preservation under the existing requirement before claiming they are reserved.
  • ClientStates::draft_mut and Application::vault_draft_mut have no external production consumers. The latter currently exists in the public API solely to support one colocated app test. App add_client and open have public integration-test fixture consumers, so their removals need an explicit fixture/API migration, not just a pub reduction.
  • Existing test-only container driver is src/container_driver.rs included only by src/lib.rs under #[cfg(test)]. Its normal fixture tests are synthetic; ignored container_driver::alpine_scenario_driver is opt-in and is not an ordinary host test or a systemd acceptance test. Do not run it as part of this documentation task.
  • M19.2 and the subsequent migrations use only approved dependencies already present. No dependency, manifest, lockfile, configuration, or product behavior change is implied.