Refactor acceptance and architecture handoff

This commit is contained in:
2026-10-30 23:30:16 +02:00
parent 7123197ec4
commit 4c86472740
11 changed files with 314 additions and 164 deletions
+149 -84
View File
@@ -1,9 +1,10 @@
# 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.
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
@@ -32,74 +33,144 @@ 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 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.
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 | Proposed owner | Readers / writers | Existing proof | Migration |
| Current type / field / function | Business meaning | Authority | Current owner | Readers / writers | Existing proof | Resolution |
| --- | --- | --- | --- | --- | --- | --- |
| `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 |
| `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 | Proposed owner | Readers / writers | Existing proof | Migration |
| Current type / field / operation | Business meaning | Authority | Current owner | Readers / writers | Existing proof | Resolution |
| --- | --- | --- | --- | --- | --- | --- |
| `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 |
| `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 | Proposed owner and consumers | Existing proof | Migration |
| Current projection | Business meaning / authority | Current owner and consumers | Existing proof | Resolution |
| --- | --- | --- | --- | --- |
| `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 |
| `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 | Proposed owner | Readers / writers | Existing proof | Migration |
| Current context | Meaning and authority | Current owner | Readers / writers | Existing proof | Resolution |
| --- | --- | --- | --- | --- | --- |
| 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 |
| 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` (`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. |
| `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](../evidence/milestone-22.md) 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](../evidence/milestone-22.md) 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
@@ -124,7 +195,7 @@ saved-file or runtime mutation; drafts may remain in memory where specified.
| 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` |
| 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
@@ -153,7 +224,7 @@ saved-file or runtime mutation; drafts may remain in memory where specified.
| 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` |
| 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
@@ -167,49 +238,43 @@ saved-file or runtime mutation; drafts may remain in memory where specified.
| 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
## M20–M26 migration disposition
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.
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.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. |
| 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 gaps and verification notes
### 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 planned M22–M26 commands use existing
`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.
- 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.
- 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