### Milestone 27 — Integrated refactoring acceptance and architecture handoff
Status: pending. Prerequisite: M26 accepted.
Status: accepted (2026-10-30T23:21:10+02:00; independent R1–R4 re-review, A4 evidence check and final integrated Rust gates passed). Prerequisite: M26 accepted. Initial tree clean at `7123197`; pinned Rust/Cargo 1.97.1, rustfmt 1.9.0 and Clippy 0.1.97 verified. This session's coordinator GPT-6-Sol/medium was user-confirmed; project configuration and explicit M27 worker dispatch selected GPT-6-Luna/high. M1–8 and M10–26 are accepted; M9 remains retired/unaccepted by user direction and is not a prerequisite.
#### Outcome and scope
@@ -4138,9 +4138,9 @@ Verify the integrated refactoring against the original behavior map and demonstr
| M27.3 | repairer; reviewer; coordinator | pending | Bounded repairs, final integrated gates and handoff |
| M27.1 | implementer `/root/m27_implement` | complete | Reconciled architecture map, reading guide, glossary and layout; caller audit retained active fixture APIs; DOC checks passed; no Rust edits |
| M27.2 | fresh reviewer `/root/m27_review` | complete | A2 passed on source inspection; M27-R1/R3 actionable documentation/comment findings and R2 plan consistency finding recorded |
| M27.3 | repairer`/root/m27_repair`; reviewer; coordinator | complete | R1–R4 closed; six focused suites, final RUST gates and independent A4 evidence/plan check passed |
1. Compare every M19 source/test mapping with actual final owners. Remove obsolete forwarding APIs only after checking all binary, library, integration and container-driver callers. Remove stale milestone comments that incorrectly describe implemented paths as future work. Do not delete historical evidence or turn old verification limitations into new success claims.
2. Record a concise reading guide for create -> Commit -> first delivery -> explicit restart, delete -> Commit -> verified address release, and preview -> confirm restore. For each, name the entry point, rule owner, side-effect boundary and failure tests. A reviewer must be able to follow the scenario without discovering hidden UI authorization or independent metadata authority. Record justified exceptions instead of hiding them behind a size metric.
@@ -4150,16 +4150,16 @@ Verify the integrated refactoring against the original behavior map and demonstr
#### Acceptance and validation
- [] M27-A1: all M19 mappings resolved to actual owners/tests; glossary/layout/reading guide agree with code; obsolete bypasses removed or explicitly justified.
- [] M27-A2: integrated independent audit finds no unresolved behavior, authority, authorization or secret-handling regression.
- [] M27-A3: preserved-format synthetic fixtures and all required host RUST gates pass on final source; exact outcomes and separate Alpine/VM limitations recorded.
- [] M27-A4: milestone register, outcomes and current resume point agree; evidence contains reproducible source identity and final review disposition.
- [x] M27-A1: all M19 mappings resolved to actual owners/tests; glossary/layout/reading guide agree with code; obsolete bypasses removed or explicitly justified.
- [x] M27-A2: integrated independent audit finds no unresolved behavior, authority, authorization or secret-handling regression.
- [x] M27-A3: preserved-format synthetic fixtures and all required host RUST gates pass on final source; exact outcomes and separate Alpine/VM limitations recorded.
- [x] M27-A4: milestone register, outcomes and current resume point agree; evidence contains reproducible source identity and final review disposition.
Evidence: docs/evidence/milestone-27.md. Outcome: not accepted. Handoff: stop after scoped acceptance; no subsequent milestone is authorized by this plan.
Review and repair: independent M27-R1 (missing `display_projection` caller audit), R2 (stale resume point), R3 (implemented paths described as future) and R4 (fixture-only public backup API) were repaired and re-reviewed. R4's first repair exposed a Clippy dead-code child helper; the bounded follow-up made both levels test-only, and final Clippy/full gates passed. Independent reviewer confirmed A4 after verifying evidence hashes, artifact, gates and plan consistency. [M27 evidence](docs/evidence/milestone-27.md) records source identity, exact host results and Alpine/VM limits. Outcome: accepted for host/synthetic refactoring scope. Handoff: stop after M27; no subsequent milestone is authorized by this plan.
## Current progress and resume point
- M26 is accepted for host/synthetic scope: app-owned secret-free facts and per-action eligibility drive TUI presentation, while the command adapter owns prepared contexts and the worker keeps generation/lifecycle control. Independent R1–R4 closure and required Rust gates passed. [M26 evidence](docs/evidence/milestone-26.md) records final inputs and limits. M27 remains pending; M9 remains retired/unaccepted and its real-system limitations remain unchanged.
- M27 is accepted for host/synthetic refactoring scope after independent R1–R4 closure, six focused suites, final Rust gates and A4 evidence/plan verification. [M27 evidence](docs/evidence/milestone-27.md) identifies tested source and limitations. Stop here; no next milestone or deployment is authorized. Current Alpine revalidation and disposable-VM systemd checks remain incomplete; M9 remains retired/unaccepted.
- M18 was accepted on 2026-10-29: structured, accurate in-app Help, responsive wrapping and bounded scrolling passed independent R1/R2 closure, synthetic/PTY checks and pinned Rust completion gates. [M18 evidence](docs/evidence/milestone-18.md) records final inputs and limits. At that handoff, M19 had not started.
@@ -4188,7 +4188,7 @@ Evidence: docs/evidence/milestone-27.md. Outcome: not accepted. Handoff: stop af
- Milestone 8 delivered reviewed opt-in Alpine Testcontainers coverage for real IPv4 WireGuard peers, application lifecycle, saved/runtime separation, restoration, import/reissue, backup failures and the real Rustix reader. Host gates and three fresh real-container runs passed on one stable source fingerprint; M8-A1–A10 accepted. Real IPv6 behavior is unverified by user-authorized scope.
- Milestone 9 is retired and unaccepted: the reviewed disposable VM preparation and guarded first start did not establish Debian/systemd acceptance. The exact VM was powered off after the user stopped work. M9.2–M9.3 were not started; M9-A1–A9 are unchecked.
- M18–M26 handoffs remain complete. Stop after M26; begin M27.1 only on a separate request. Keep M16 validation limitations separate from TUI evidence. Do not access production, commit, deploy or resume VM work.
- M18–M27 handoffs are complete. Stop after M27 acceptance; no next milestone is authorized. Keep M16 validation limitations separate from TUI evidence. Do not access production, commit, deploy or resume VM work.
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
```
- 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
| `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; mutableidentity only for an unambiguous manageable peer | App-owned readmodel; `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 |
| `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; apprestart 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 andrestore 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 becausesession 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
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.
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.
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.
/// An event delivered by the future D-Bus adapter.
/// An event emitted by the D-Bus adapter and consumed by the job tracker.
#[derive(Clone, Debug, Eq, PartialEq)]
pubenumServiceEvent{
JobRemoved{
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.