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
+87 -59
View File
@@ -1,19 +1,13 @@
# Project Layout
Read this document when creating or reorganizing modules. The layout below
reflects the current package; sections marked planned describe later test and
integration work only.
This is the integrated package layout after M26. Paths below exist in the
repository; scenario descriptions name current owners. Add modules when a
cohesive responsibility needs them, not as placeholders.
## Target project layout
The repository has one Cargo package with a binary (`src/main.rs`) and a
library (`src/lib.rs`). Startup and process exit remain in `main.rs`; testable
application logic and the management TUI are library modules. A library target
does not require publishing a separate library package. Introduce a workspace
only when independently reusable or separately built components justify it.
Implemented modules include the following. Add modules as implementation
requires them; do not generate empty scaffolding for future integration work.
The repository has one Cargo package with a binary (`src/main.rs`) and a library
(`src/lib.rs`). Startup and process exit remain in `main.rs`; testable
application logic and the TUI are library modules. The library target is not a
separately published package.
```text
Cargo.toml
@@ -23,64 +17,98 @@ AGENTS.md
CONTEXT.md
PLANS.md
README.md
.gitignore
src/
main.rs # Startup, arguments, process exit
lib.rs # Module declarations and application API
config.rs # Application settings and path resolution
domain.rs # Clients, identities, address allocation/reservations, change states
app.rs # Application operations and orchestration
lib.rs # Library modules; test-only container driver gate
startup.rs # Root boundary, settings/store loading, session entry
config.rs # Settings validation and executable-relative paths
domain.rs # Client projection, address allocation/reservations, apply knowledge
app.rs # Application facade, session, and public operation types
app/
draft.rs # Coordinated in-memory document, client-state, and vault drafts
first_delivery.rs # Session-local first-issue authorization and retry state
first_file.rs # Password-free first-file and optional QR scenario
draft.rs # Coordinated document/client/vault draft replacements
first_delivery.rs # Session-local one-use capability and retry state
first_file.rs # First-delivery file and optional QR scenario
reissue.rs # Password-per-action saved-client reissue
read_model.rs # Secret-free application facts and per-action eligibility
reissue.rs # Per-action vault authentication and saved-client delivery
commit.rs # Prepare, backup, install, and publish saved-state Commit
restore.rs # Bound preview and confirmed saved-state replacement
backups.rs # Manual/pre-operation backup and archive-list scenarios
restart.rs # Bound restart preview, backup, job, verification, and publication scenario
commit.rs # Backup, saved install, and Commit publication
restore.rs # Preview, reservation rebuild, and confirmed saved replacement
backups.rs # Manual/pre-operation backup and archive listing
restart.rs # Restart preview, backup/job/verify/publication sequence
wireguard.rs # Server configuration module boundary
wireguard/
document.rs # Parsing and edits preserving original text
metadata.rs # Managed client comment blocks
vault.rs # Encryption, import, secret retrieval
backup.rs # Backup archives and restoration
runtime.rs # Running WireGuard interface and statistics
service.rs # Systemd operations and restart verification
delivery.rs # Client configuration files and QR generation
tui.rs # Terminal UI entry point
document.rs # Lossless parse, peer inspection and document authority
edit.rs # Byte-preserving peer and metadata edits
metadata.rs # Managed comment grammar and metadata validation
vault.rs # Vault format, encryption, key matching and retrieval
vault/
source.rs # Read-only configured client-source access
import.rs # Client-file reconciliation and encrypted import
persistence.rs # Saved-file installation, recovery and state codec
backup.rs # Versioned archive encoding, parsing and preview
runtime.rs # Runtime observations, peer projections and comparison
runtime/
netlink.rs # Bounded Generic Netlink WireGuard reader
transport.rs # Read-only runtime transport
service.rs # Systemd request and job completion adapter
service/
dbus.rs # D-Bus systemd implementation
delivery.rs # Client configuration rendering and adapter boundary
delivery/
files.rs # Secure client file publication
qr.rs # Terminal QR generation
tui.rs # TUI entry and session lifecycle
tui/
state.rs # Screen state and navigation
events.rs # Commands, worker lifecycle, and background events
state.rs # Screens, selection, UI generations and navigation
events.rs # Typed commands, worker queue and lifecycle
events/
executor.rs # Application command adaptation and prepared contexts
forms.rs # Short-lived forms and secret owners
render.rs # Rendering
terminal.rs # Terminal setup and cleanup
executor.rs # Application command adaptation and prepared contexts
forms.rs # Short-lived form inputs and sensitive commands
render.rs # Visual strings, styles, wrapping and layout
terminal.rs # Terminal setup, event loop and cleanup
container_driver.rs # Test-only Alpine scenario driver (`cfg(test)`)
tests/
config_preservation.rs # Untouched server configuration remains identical
commit_restore.rs # Persistence, backups, restore, runtime isolation
client_delivery.rs # Client file delivery and lifecycle workflows
vault_import.rs # Existing-client import and reconciliation workflows
tui_workflow.rs # Synthetic worker-level TUI workflows
fixtures/ # Synthetic configuration and archive inputs
support/
mod.rs # Shared test adapters and helpers
config_preservation.rs # Synthetic byte-preservation behavior
commit_restore.rs # Persistence, backups, restore and runtime isolation
client_delivery.rs # First delivery, retry, reissue and lifecycle behavior
runtime_restart.rs # Restart scenario behavior using controlled adapters
vault_import.rs # Import and reconciliation behavior
tui_workflow.rs # Worker-level TUI workflows and synthetic PTY checks
testcontainers_wireguard.rs # Explicitly selected, ignored Alpine integration suite
support/ # Shared fixtures, adapters and container harness
fixtures/wireguard/ # Synthetic server configuration
containers/alpine/ # Pinned Alpine runtime image and readiness support
vm/ # Guarded disposable-VM preparation/test scripts
config/
wireguard-admin.example.toml # Safe example; settings format chosen at implementation
wireguard-admin.example.toml # Safe distributable example settings
docs/
SPEC.md
IMPLEMENTATION.md
PROJECT_LAYOUT.md
architecture/domain-refactoring.md
adr/
target/ # Cargo output; never commit
target/ # Cargo output; never commit
```
- Use modules named for their responsibility. Avoid catch-all `utils`, `helpers`, or `common` modules; place supporting code with its owner.
- Begin with a single module file and split into child modules when needed. Prefer `name.rs` with `name/child.rs` for production modules; `tests/support/mod.rs` keeps test helpers from becoming a separate integration test target.
- Declare application modules once in the library, rather than duplicating them in both `main.rs` and `lib.rs`. Keep internals private or `pub(crate)`; expose only the library API needed by the binary and behavioral integration tests.
- Keep focused unit tests beside their modules under `#[cfg(test)]`; put cross-module behavioral tests in `tests/`. Use `tests/support/` for test-only adapters rather than exporting test infrastructure from the production library.
- The `config/` directory contains distributable examples, not live settings. The example filename above is illustrative and does not settle the settings format. Actual settings remain alongside the deployed executable as specified.
- Keep live server configurations, private keys, delivered client files, and runtime `Backup/` data out of the source tree and version control. Ignore generated build output and local deployment data without hiding synthetic fixtures.
- Add `examples/` only for runnable Rust examples and `benches/` only when benchmarks are needed. Do not add empty directories or scripts that duplicate Cargo commands.
- Keep the layout aligned with actual module responsibilities as the code grows. Update this section when files move; do not preserve obsolete paths merely to match the initial tree.
## Ownership rules
- `Application` is the public facade. Scenario ordering belongs in `src/app/`
owners; the facade delegates without transferring authority to the TUI.
- The server `ConfigDocument` is authoritative for peers and metadata.
`ClientStates` is only the manageable-client projection. Editing preserves
unowned bytes and comments.
- Domain logic does not perform file, crypto, runtime, service, or rendering
work. Persistence owns installed state; backup owns archive encoding;
runtime owns observations/comparison; service owns systemd calls.
- `app/read_model.rs` supplies secret-free facts and action eligibility.
TUI state owns selection/navigation/generation; the executor binds prepared
continuations and calls application operations; renderers own presentation.
- Keep module internals private or `pub(crate)` where possible. Public facade
APIs retained for integration fixture setup are documented with their caller
audit in [the architecture map](architecture/domain-refactoring.md).
- Keep focused unit tests beside their modules and cross-module behavior in
`tests/`. `src/container_driver.rs` is included only in the library test
harness. Its opt-in Alpine application fixture is not systemd acceptance.
- `config/` holds safe examples, not live settings. Runtime data and archives
remain outside the source tree. Do not commit live configuration, keys,
delivered client files, or generated `Backup/` data.
- Keep this map aligned with actual files and responsibility. Do not add empty
directories or speculative traits/modules.
+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
+54
View File
@@ -0,0 +1,54 @@
# Milestone 27 evidence — integrated refactoring handoff
Recorded 2026-10-30T23:18:12+02:00. Baseline HEAD: `7123197ec4a3fad74b71f5c7b598f1d6fd74d3a1`, initially clean. Final working tree is uncommitted and changes `CONTEXT.md`, `PLANS.md`, `docs/PROJECT_LAYOUT.md`, `docs/architecture/domain-refactoring.md`, `src/app.rs`, `src/app/backups.rs`, `src/app/first_file.rs`, `src/app/restart.rs`, `src/runtime/netlink.rs`, and `src/service.rs`. No Cargo manifest, lockfile, dependency, feature, persisted codec, archive, vault, settings-default, WireGuard editor, or fixture file changed in M27. Rust/Cargo 1.97.1, rustfmt 1.9.0, Clippy 0.1.97, native host target. All tests below used synthetic fixtures/PTY only; no production configuration, keys, VPN, host service, Docker daemon, or VM was accessed.
## Final inputs
| Input | SHA-256 |
| --- | --- |
| `CONTEXT.md` | `7dcfa4d86f70fb850f3416d6659275ee28237b5075c5beed82d2a33ad44150a4` |
| `docs/PROJECT_LAYOUT.md` | `f973207e71f20c6c912105c16546a51150acf63551a7f196ecd38d2101e9a44b` |
| `docs/architecture/domain-refactoring.md` | `45ad18a43b224f318b0a7e55548d47f7ccea8e2e719e3378ef35f419e3ec8391` |
| `src/app.rs` | `e235ff788c1b5f1ac0ac9951a9c4bb578205dfc65903d3ae80ed9f3142fb38da` |
| `src/app/backups.rs` | `b3b01289b20e262c24cfce79c507fe4d3761102df4863a11b3a90b2398b0692f` |
| `src/app/first_file.rs` | `d41fa3e2d2150c401ceee9caaeb7268b741a970105e5266123334cd78d977073` |
| `src/app/restart.rs` | `5d26cb877fd8a8211549e15868ccd450729ce3ecb54ea954efd15b379f5a2ce0` |
| `src/runtime/netlink.rs` | `396f4726880c7cf54764d6ff0c67aa1ab2d6c66cd36af7b1be4ca0e04862bab3` |
| `src/service.rs` | `e7c7a4367e2a0dc8a87113e3083d8998e7aebb56f6d4c32d5c763b9f239fa8eb` |
| `target/release/wireguard-admin` | `cb6e995d64a7cb7537c5fb17e86d609ca62fff150f519a970febd0800b0d4177` |
Source-input fingerprint from `rg --files src tests/support tests/containers tests/testcontainers_wireguard.rs Cargo.toml Cargo.lock | sort | xargs sha256sum | sha256sum`: `1b746e31e2e8c96f511c34b51e417b486d65fa0cc17983fa15e12882dd9c6a02`. This includes the final test-only backup visibility repair and identifies the host-tested source independently of the dirty plan/evidence files.
## Architecture and preservation audit
M19's baseline source was recorded against `23d6180f2478f636c185d67b8bb3788d21656ce3`. The coordinator inspected `git diff --stat` from that commit through M26 HEAD and the changed production/format-sensitive file list; that diff spans the M20–26 application/domain/TUI moves and changes only persistence test references, not its codec. `Cargo.toml`, `Cargo.lock`, settings defaults, backup/vault/byte-preserving editor implementation, and synthetic fixture files did not change in that baseline-to-M26 diff. The independent reviewer inspected the integrated refactor against the M19 map, current source and shared preservation matrix, including authority, public identity, unsupported/ambiguous peers, original imports, retained plaintext discipline, short-lived delivery secrets, saved/runtime separation, restart uncertainty, address release, TUI eligibility/generation and public visibility. No unresolved behavior, authorization, or secret-handling regression was found. Historical M19 restore-reservation gap was closed by M23 and is now mapped to actual tests and owner.
The architecture document now gives the three requested end-to-end reading paths with entry point, rule owner, side-effect boundary and failure tests, and resolves each M19 owner/transition to actual M20–26 modules/tests. The glossary remains domain-only and the layout lists present files. `ClientStates::draft_mut` and `Application::vault_draft_mut` were already removed. `Application::open`, `add_client`, and `display_projection` have active unit/integration or test-only container callers and remain with explicit caller/retention rationale; production startup/create uses the intended methods. A fixture-only pre-restart backup facade and its helper were restricted to private `#[cfg(test)]` code. Stale comments describing implemented confirmation, D-Bus and netlink flows as future work were corrected. No test assertion was weakened.
Existing preserved-format and behavior coverage includes the focused `config_preservation`, `commit_restore`, `client_delivery`, `runtime_restart`, `vault_import`, and `tui_workflow` suites below, plus persistence, archive, vault, domain, service and application tests in the full suite. Old fixture inputs still load with unchanged meaning under these host checks; this is synthetic compatibility evidence, not a new real-system run.
## Integrated checks
All commands ran in the repository root after the final source repair. Exit 0 unless specified.
| Command | Actual result |
| --- | --- |
| `cargo test --locked --test config_preservation` | 10 passed, 0 failed |
| `cargo test --locked --test commit_restore` | 9 passed, 0 failed |
| `cargo test --locked --test client_delivery` | 5 passed, 0 failed |
| `cargo test --locked --test runtime_restart` | 3 passed, 0 failed |
| `cargo test --locked --test vault_import` | 5 passed, 0 failed |
| `cargo test --locked --test tui_workflow` | 9 passed, 0 failed |
| `cargo fmt --all -- --check` | Exit 0 |
| `cargo clippy --locked --all-targets -- -D warnings` | Exit 0 |
| `cargo test --locked` | Exit 0; library 352 passed, 2 ignored; integration and 2 doc tests passed; opt-in Alpine tests stayed ignored |
| `cargo build --locked --release` | Exit 0; artifact hash above |
| `git diff --check` | Exit 0 |
The first final-gate attempt after making the fixture facade test-only found Clippy dead code in its child helper. The repair made the child helper test-only too; the entire table above was rerun on that final source. The initial failed attempt is diagnostic history, not a passing gate.
## Independent review and limits
Fresh reviewer `/root/m27_review` was separate from implementer `/root/m27_implement`; repairer `/root/m27_repair` made bounded fixes. R1 (missing `display_projection` caller audit), R2 (stale plan resume point), R3 (future-work source comments), and R4 (fixture-only public backup API) were repaired and re-reviewed. R4's first repair exposed the Clippy dead-code helper; the second repair and final gates closed it. The reviewer independently verified the final source, fingerprint and binary hashes, reran formatting, Clippy, full tests, release build and diff check, and confirmed M27-A4 plan/evidence consistency after the register correction. No scoped review finding remains.
M8's last Alpine result used fingerprint `b28dc6e4cb92aa6edf384d966ba5952a8d6d0efe4c22d6a804b67fb2c0297da2`; today's final source fingerprint differs. The M8 run is historical and **does not validate this source**. No explicitly selected current disposable daemon/driver was provided for M27, so Alpine revalidation is incomplete and no Alpine pass is claimed. The retired M9 disposable-VM/systemd checks remain unverified; Alpine would not prove actual systemd/D-Bus behavior. Real IPv6 capability/traffic and release readiness are also unverified. M27 acceptance is limited to host/synthetic scope.