Files

54 KiB
Raw Permalink Blame History

Domain and application refactoring contract

Status: integrated architecture map and handoff after M26. M20–M26 moved the responsibilities below to their current owners. This document records verified source ownership and behavioral proof; it does not authorize behavior, file-format, dependency, or product changes. M27 review and acceptance remain pending.

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.

Reading guide: common workflows

These paths identify the application entry, the rule owner, the side-effect boundary, and tests that exercise failures as well as success.

Create, Commit, first delivery, explicit restart

  • Entry: TUI UiCommand::CreateClient, Commit, DeliverFirstIssue, and PreviewRestart followed by the explicit restart choice; the adapter is src/tui/events/executor.rs. The facade methods are Application::prepare_new_client, commit, deliver_first_issue_file, preview_restart, and restart.
  • Rule owners: app/draft.rs coordinates the draft; app/commit.rs owns Commit; app/first_delivery.rs owns the one-use session capability and retries; app/first_file.rs owns first-file output; app/restart.rs owns backup, job completion, comparison and durable apply publication. The app read model reports eligibility; the TUI does not grant it.
  • Effect boundary: creation changes memory only. Commit backs up and installs saved files/vault, without a runtime call. First delivery writes the selected client file and may display its QR. Only the later explicit restart may call the service/runtime adapters; addresses and current applied knowledge change only after complete verification and durable bookkeeping.
  • Failure proof: app::new_client_preparation_is_transactional_and_uncommitted_keys_cannot_issue, app::commit_failure_keeps_pending_issue_and_successful_retry_authorizes_it, app::failed_file_publication_can_retry_in_a_different_directory, app::restart_backup_failure_preserves_draft_and_submits_no_job, app::restart_mismatch_and_post_restart_write_failure_never_release_addresses.

Delete, Commit, verified address release

  • Entry: UiCommand::DeleteClient, then Commit, then PreviewRestart and a confirmed restart choice through the executor; facade methods are Application::delete_client, commit, and restart.
  • Rule owners: app/draft.rs stages peer removal; ConfigDocument in wireguard/document.rs and wireguard/edit.rs owns peer identity and byte-preserving edits; domain.rs owns pending/committed reservations and allocation; app/commit.rs persists the deletion; app/restart.rs publishes release only after runtime.rs verifies the full saved peer set and durable state install succeeds.
  • Effect boundary: delete is draft-only. Commit backs up and changes saved files/vault but leaves the running interface unchanged. The address remains reserved through any restart failure or mismatch and is released only after verified publication.
  • Failure proof: app::deletion_of_unprojectable_peer_persists_its_exact_host_route_reservation, app::deletion_of_unprojectable_peer_with_broad_route_fails_without_draft_change, commit_restore::backup_failure_blocks_commit_and_restore_without_losing_drafts_or_saved_bytes, app::restart_mismatch_and_post_restart_write_failure_never_release_addresses.

Preview and confirm restore

  • Entry: TUI UiCommand::PreviewRestore, then ConfirmRestore or cancel; src/tui/events/executor.rs binds the preview to its worker generation. Facade methods are Application::preview_restore and confirm_restore.
  • Rule owners: app/restore.rs validates the archive, rebuilds internal reservations and coordinates confirmed replacement; backup.rs owns archive validation/encoding; persistence.rs owns safe installation and recovery. The selected archive's settings, configuration and vault remain authoritative for restored saved state.
  • Effect boundary: preview and cancel are read-only. Confirmation requires a fresh precautionary backup, then replaces saved files/vault as a whole. It resets current apply knowledge to Unknown and does not call the running VPN or request a vault password.
  • Failure proof: app::preview_is_read_only_and_confirm_restores_without_unlocking_vault, app::restore_accepts_changed_non_path_settings_and_rejects_changed_paths, app::restore_does_not_reserve_unprojectable_exact_hosts_owned_by_restored_document, commit_restore::backup_failure_blocks_commit_and_restore_without_losing_drafts_or_saved_bytes, events::prepared_delivery_and_restore_confirmations_are_bound_to_the_reviewed_generation.

Current concepts and migration map

References use current repository-relative module paths. The M19.1 map is resolved below against the integrated M20–M26 implementation. Test names point to existing behavioral coverage; this documentation update does not claim those tests were rerun.

Application session fields and workflows

Current type / field / function Business meaning Authority Current owner Readers / writers Existing proof Resolution
Application::store (src/app.rs) Saved-file boundary, configured paths, validation and install capability SavedStore paths and installed files Application facade; scenario owners in app/commit.rs, app/restore.rs, app/backups.rs, app/restart.rs Read by application scenarios; saved files change only through saved-operation owners commit_restore::backup_failure_blocks_commit_and_restore_without_losing_drafts_or_saved_bytes; persistence complete_install_publishes_components_with_private_modes Resolved M23/M25
Application::saved (src/app.rs) Exact installed generation held by this session SavedBytes read from SavedStore Application session; bound contexts in app/restore.rs and app/restart.rs 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 Resolved M23/M25
Application::draft (src/app.rs, src/app/draft.rs) and its document Coordinated in-memory document, client projection, vault, and draft-only reservations Authoritative server document plus encrypted vault; ClientStates is a restricted projection app/draft.rs stages and publishes coordinated replacements; wireguard/edit.rs owns byte-preserving edits Draft operations prepare replacements; Commit serializes the staged values app::new_client_preparation_is_transactional_and_uncommitted_keys_cannot_issue; config_preservation::edits_keep_interface_and_unowned_bytes_exact_and_can_be_reparsed Resolved M22
Application::states (src/domain.rs) Narrow draft/saved management snapshots, current apply knowledge, and address reservations Document projection for manageable clients plus persisted internal state Domain ClientStates and AddressReservations; persistence only encodes/decodes durable fields App draft/Commit/restart/reopen; read model exposes secret-free facts domain::discard_restores_draft_and_commit_keeps_applied_snapshot_separate; domain::successful_verified_apply_releases_deleted_addresses_and_mismatch_does_not; persistence::persisted_history_never_marks_the_runtime_verified Resolved M20–M22
Application::draft.vault (src/app/draft.rs) Encrypted vault draft used for secret encryption, import, deletion, and reissue Installed vault bytes at open, then current app draft app/draft.rs coordinates; cryptographic/value operations stay in vault.rs and vault/import.rs App setup/import/create/delete/Commit/reissue 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 Resolved M22/M24
Pending exact-host reservations (ClientStates::address_reservations, src/domain.rs) Draft-only exact host addresses retained when a saved peer cannot project to Client Saved document peer routes and pending deletion Domain AddressReservations, coordinated by app/draft.rs 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 Resolved 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 Resolved 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 Resolved M21/M24–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 Resolved M20/M25
FirstDeliverySession (src/app/first_delivery.rs) 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/first_delivery.rs owns session capability state; app/first_file.rs runs first-file output; vault owns ciphertext/crypto and delivery adapters own publication Coordinated creation registers; Commit promotes; file 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 Resolved M20/M22/M24
open_from_saved_store, open (src/app.rs) Construct a session from installed saved bytes; injected-state open exists for fixture/state-seeding callers. Reopen is Unknown regardless of history. Installed files/document; persisted reservations/history Application session constructors Production startup uses open_from_saved_store; open is still called by library unit and integration fixtures startup::management_session_loads_supported_state_and_keeps_unsupported_peers_visible; app::verified_history_persists_across_reopen_without_marking_runtime_applied Resolved M22; retained fixture API, see caller audit
prepare_new_client, add_client, rename_client, delete_client, discard_draft (src/app.rs) Stage client/document/vault changes without saved-file or runtime effects; add_client is a legacy fixture helper and does not issue a key/vault secret Document plus restricted client projection and vault app/draft.rs, application facade and domain/editor/vault owners; no separate app/clients.rs was needed TUI uses prepared create/rename/delete/discard; test fixtures still call add_client 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 Resolved M22; retained fixture helper, see caller audit
commit, preview_restore, confirm_restore (src/app.rs) 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 Resolved M23
preview_restart, restart, verified-result publication (src/app.rs, src/app/restart.rs) 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 in service.rs; comparison in runtime.rs; durable fields in persistence TUI previews/confirms; fixture apply callback is #[cfg(test)]; 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 Resolved M20/M21/M25

Domain state and transitions

Current type / field / operation Business meaning Authority Current owner Readers / writers Existing proof Resolution
Client / ClientAddresses / Snapshot (src/domain.rs) 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 value types in domain.rs; no child module split was needed 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 Resolved M20/M21
ClientStates::draft / saved (src/domain.rs) 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 and Commit advances saved; TUI consumes read-model facts domain::discard_restores_draft_and_commit_keeps_applied_snapshot_separate; app::commit_advances_saved_state_without_applying_and_backup_contains_old_bytes Resolved M21/M22
applied_snapshot / ApplyStatus Optional management snapshot last verified plus current knowledge; 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 Resolved M20/M21
reserved_addresses and restore_persisted_reservations (src/domain.rs) Persisted conservative addresses that cannot be allocated Prior saved peer routes and internal state domain.rs owns reservation 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 Resolved M21
Narrow stage_client, remove_draft_client, commit_draft, discard_draft (src/domain.rs) Mutate the manageable draft projection through scoped operations and preserve saved/applied separation ClientStates snapshots, ultimately document-derived Application draft coordinator calls crate-private stage/remove and public commit/discard transitions No unrestricted draft_mut accessor remains; direct mutation in domain tests is limited to transitions domain::discard_restores_draft_and_commit_keeps_applied_snapshot_separate; app::discard_restores_draft_without_writing_installed_bytes; app::commit_failure_keeps_pending_issue_and_successful_retry_authorizes_it Resolved M21/M22; escape hatch removed
Verified apply, technically verified apply, runtime unknown/unavailable transitions (src/domain.rs) Change present apply knowledge only after verified result publication; preserve current uncertainty and optional management projection Complete runtime comparison plus successful persisted bookkeeping Domain transitions are crate-private and called by the application restart scenario App restart success/failure/reopen; statistics reads cannot call transitions 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 Resolved M20/M21
allocate_ipv4, allocate_ipv6, AddressAvailability (src/domain.rs) Pure address allocation against server, occupied and reserved sets Validated settings and document-derived address sets Domain address allocator and 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 Resolved M21

Peer and runtime projections

Current projection Business meaning / authority Current owner and consumers Existing proof Resolution
PeerInspection, PeerIssue, DocumentIssue, ConfigDocument (src/wireguard/document.rs) 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 Resolved M22/M25/M26
ClientDisplay, SessionDisplay, ApplicationView (src/app/read_model.rs) Secret-free current draft-document rows and authoritative per-action eligibility; mutable identity only for an unambiguous manageable peer App-owned read model; Application facade exposes it tui::enter_opens_an_unsupported_peer_for_inspection_without_mutation_identity; tui::issues_screen_hides_valid_address_shape_limits_but_keeps_genuine_errors Resolved M26
SessionView, ClientStateRow, VisibleRecord (src/tui/state.rs) Maps ApplicationView into UI navigation state; keeps generation, selection, screens and rows, without reparsing saved bytes or constructing authority TUI state consumes app facts and owns UI selection/navigation 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 Resolved M26
Renderer-derived labels and issue text (src/tui/render.rs) Styles and renders app facts; no operation eligibility or peer authority is inferred from labels Renderer owns visual strings, styling, wrapping, geometry and layout 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 Resolved M26
SavedPeerProjection, RuntimeObservation, TechnicalMatch (src/runtime.rs) 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 exposes only safe status/findings 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 Resolved M20/M25/M26

Authorization, prepared contexts and worker generations

Current context Meaning and authority Current owner Readers / writers Existing proof Resolution
UI selection guard UiState::selected_client_mutable (src/tui/state.rs) 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 Resolved M26
Per-action UI prerequisites (src/tui/terminal.rs, src/app/read_model.rs) Selection is presentation-only; rename/delete/delivery/reissue availability comes from typed app eligibility, and each operation revalidates on execution App read model owns availability facts; terminal maps them to actions and hints tui::state binds selected row; app methods and config editor validate mutation 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 Resolved M26
First-issue session context (src/app/first_delivery.rs, src/app/first_file.rs) Session-local secret and one-use authorization bound to identity, peer directives and encrypted record; retry state reflects publication/cleanup uncertainty app/first_delivery.rs owns session lifecycle; app/first_file.rs owns first-file scenario; adapters own crypto and publication Create registers; Commit enables; delivery validates/consumes; edits/delete/discard invalidate 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 Resolved M20/M24
ApplicationExecutor prepared destination and restore preview (src/tui/events/executor.rs) Worker-owned continuation state bound to target, delivery mode/QR choice, attempt, preview and request generation Private named contexts in the executor child Executor command handling; 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 Resolved M20; both contexts use named structs
PendingOperation, SessionView.generation, worker generation (src/tui/events.rs, src/tui/state.rs) 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 Resolved M20/M26
FormController, SecretInput, SecretText, SensitiveCommand (src/tui/forms.rs, src/tui/events.rs) 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 Resolved M24/M26; lifetimes preserved

Public API escape-hatch callers and removal conditions

API Actual callers Safe path and timing
ClientStates::draft_mut No current declaration or callers. Former unrestricted accessor was replaced by crate-private stage_client/remove_draft_client and narrow transitions in src/domain.rs; full rg audit finds no production, test, startup, binary, integration or container-driver reference. Removed in M22 after caller audit; no replacement is needed by callers. M22 evidence records the review.
Application::vault_draft_mut No current declaration or callers. The public mutation bypass was removed and no test-only API replaced it. Removed in M22; vault state is mutated through coordinated application operations. M22 evidence records the review.
Application::add_client (src/app.rs) Unit-test setup in src/app.rs; integration-fixture setup in tests/commit_restore.rs and tests/runtime_restart.rs. No binary, startup, production module, tests/support, or src/container_driver.rs caller. ConfigDocument::add_client remains the actual byte-preserving editor used by prepare_new_client and preservation tests. Retained as a public compatibility/test-seeding operation because current integration fixtures still use it. It does not generate/encrypt a private key and must not be mistaken for the normal create workflow. Removal requires deliberate fixture migration.
Application::open(store, states) (src/app.rs) Unit tests and fixture helpers in tests/client_delivery.rs, tests/commit_restore.rs, tests/runtime_restart.rs, and tests/tui_workflow.rs. No binary/startup or production caller; no container-driver caller. Retained for tests that seed ClientStates and apply knowledge. Production startup uses open_from_saved_store; fixture construction is the only current consumer class.
Application::open_from_saved_store (src/app.rs) Binary startup through startup::open_management_session; application/TUI/integration tests; container_driver::open_fixture_application in src/container_driver.rs, which is included only under #[cfg(test)] in src/lib.rs. Required production and fixture entry point; retain. Container driver normal tests are synthetic; its ignored Alpine scenario is opt-in and is not a systemd acceptance test.
Application::display_projection (src/app.rs) No production-module, binary, or actual startup-path call. startup::tests::management_session_loads_supported_state_and_keeps_unsupported_peers_visible calls it after exercising session startup. There are no tests/ integration-test calls. The only other direct callers are in src/container_driver.rs's #[cfg(test)] synthetic lifecycle test phase_protocol_keeps_creation_commit_and_first_delivery_in_one_app_session; the ignored Alpine scenario does not call it. Current TUI presentation uses Application::read_model. Retain for now as a public compatibility projection: the startup unit and synthetic fixture tests still exercise its inclusion of unsupported peers. Its compatibility value and test coverage do not make it a production authorization/read path. Removal requires deliberate migration of those callers and an API compatibility decision; do not infer it is obsolete solely because M26 moved TUI reads to read_model.
Former public Application::create_pre_restart_backup (src/app.rs) Direct callers are confined to colocated app unit tests: helper test_mark_verified, manual_and_pre_restart_backups_are_listed_without_runtime_calls, and failed_verified_history_write_does_not_advance_memory_or_reopen_as_verified. No binary, integration-test, or container-driver caller. Restricted to a private #[cfg(test)] method because only fixtures use it. Production restart performs its mandatory backup within app/restart.rs; no production API exception remains.

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 archived/current persisted reservations, addresses from current manageable clients, and exact host routes from unsupported current peers; reservations are excluded for addresses still assigned by restored peers 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::restore_does_not_reserve_unprojectable_exact_hosts_owned_by_restored_document; 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 Rebuilt internal state carries archived and current persisted reservations, addresses of current manageable clients and unsupported peers' exact host routes that the restore removes; addresses still assigned by the restored configuration are excluded. Draft-only exact routes are included. 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 restore_does_not_reserve_unprojectable_exact_hosts_owned_by_restored_document; 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

M20–M26 migration disposition

This register resolves the destinations proposed by M19 against the integrated source. The milestone labels are historical; the owner column names the current code. Public facade calls remain where the caller audit below documents an active integration-fixture dependency. 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/apply vocabulary is in src/domain.rs; observations/comparison in src/runtime.rs; presentation mapping in src/tui/state.rs; PreparedDestinationContext and RestorePreviewContext are private named structs in src/tui/events/executor.rs. Semantics remain unchanged; no domain child split was justified. Historical focused checks recorded in M20 evidence; current generation-bound proof is events::prepared_delivery_and_restore_confirmations_are_bound_to_the_reviewed_generation.
M21 — reservations and apply knowledge src/domain.rs owns AddressReservations, allocation and narrow apply transitions; src/persistence.rs encodes durable state; src/runtime.rs compares technical peer state; src/app/restart.rs coordinates verified publication. No domain/reservations.rs or domain/apply.rs split was warranted; unrestricted draft mutation was removed. Persisted schema and semantics remain in persistence. domain::exact_host_routes_stay_pending_until_commit_and_discard_drops_them; persistence::persisted_history_never_marks_the_runtime_verified; app::restart_mismatch_and_post_restart_write_failure_never_release_addresses.
M22 — coordinated draft and create/edit/import src/app/draft.rs coordinates document/client/vault/reservation drafts; src/app.rs remains the facade; src/vault/source.rs reads sources and src/vault/import.rs reconciles/encrypts. No separate app/clients.rs or app/import.rs was justified. Application::open/add_client remain for injected-state and fixture setup, documented in the caller audit. app::new_client_preparation_is_transactional_and_uncommitted_keys_cannot_issue; config preservation, delivery, import and workflow integration suites.
M23 — saved Commit, backups and restore src/app/commit.rs, src/app/restore.rs, and src/app/backups.rs own scenarios; src/persistence.rs owns journaling/path safety and src/backup.rs owns archive encoding/parsing. Preview is byte-bound and saved-only. M23 added restoration of exact routes from unsupported current peers; this closed the earlier noted reservation gap. app::commit_advances_saved_state_without_applying_and_backup_contains_old_bytes; app::preview_is_read_only_and_confirm_restores_without_unlocking_vault; restore_does_not_reserve_unprojectable_exact_hosts_owned_by_restored_document; commit_restore::backup_failure_blocks_commit_and_restore_without_losing_drafts_or_saved_bytes.
M24 — delivery scenarios src/app/first_delivery.rs owns first-issue capability/retry state; src/app/first_file.rs runs first-file delivery; src/app/reissue.rs runs password reissue. src/delivery.rs, src/delivery/files.rs, src/delivery/qr.rs, and src/vault.rs are focused adapters/value owners. No app/delivery.rs was created because session capability, file delivery and saved-record reissue have separate cohesive ownership. app::failed_file_publication_can_retry_in_a_different_directory; app::temporary_cleanup_failure_requires_manual_cleanup_before_first_issue_retry; client_delivery::reissue_unlocks_installed_saved_record_each_time_and_warns_after_reopen.
M25 — restart scenario src/app/restart.rs owns preview, attempt token, backup/job/read/compare/durable-publication order; src/runtime.rs owns complete peer projection/comparison; src/service.rs owns systemd adapter; persistence owns durable state. Fixture apply is compiled only under #[cfg(test)]. No host service calls occur in ordinary fixture tests. app::confirmed_restart_orders_backup_job_read_and_durable_publication; app::service_timeout_reports_submission_and_may_still_complete; app::restart_mismatch_and_post_restart_write_failure_never_release_addresses.
M26 — read model and TUI command adapter src/app/read_model.rs owns secret-free peer/state/issue/reservation/apply facts and per-action eligibility. src/tui/state.rs owns UI navigation/generation; src/tui/events.rs and src/tui/events/executor.rs own worker lifecycle and command adaptation; forms own transient inputs; renderers own visual output. TUI does not parse saved bytes or duplicate business authorization. Split of broader terminal/render modules was deferred; their shared lifecycle/layout helpers did not justify a larger move. tui::; tui_workflow; client_delivery; M26 evidence records focused/full host results and limits.

Test inventory 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 M22–M26 work used existing targets, and a new target is needed only if acceptance finds a real behavioral gap.
  • Application projection and eligibility are covered by colocated app:: tests plus tui:: and tui_workflow:: tests; no separate integration target was needed.
  • Restore reservation coverage includes manageable saved clients, exact routes from unprojectable current peers, persisted reservations, and exact routes retained by the restored document. app/restore.rs excludes routes still owned by restored peers. The previous M19/M23 gap note is superseded by restore_does_not_reserve_unprojectable_exact_hosts_owned_by_restored_document.
  • ClientStates::draft_mut and Application::vault_draft_mut no longer exist. Application::add_client and injected-state Application::open remain public because integration fixtures use them; the caller audit above records those exceptions. They are not the production create/startup path.
  • 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.