Add refactoring contract, terminology and behavior map

This commit is contained in:
2026-10-29 22:26:25 +02:00
parent 23d6180f24
commit ede45d224a
4 changed files with 689 additions and 4 deletions
+41
View File
@@ -7,6 +7,44 @@ Terminology for managing device access to a WireGuard VPN.
**Draft**:
Changes prepared by the administrator but not yet saved through Commit.
**Saved state**:
The installed settings, server configuration, internal metadata, and encrypted
vault after Commit or restoration. Saved state may differ from the running VPN.
**Applied state**:
Current knowledge that the running interface has been verified against the
saved peer configuration. A saved file, a successful historical restart, or a
handshake alone does not establish current applied state. After reopening, the
current state is unknown until checked again.
**Server peer record**:
A `[Peer]` section in the authoritative server configuration, including its
routes, comments, and any detected issues. A malformed record may have no usable
public key and must remain available for inspection.
**Manageable client**:
A server peer that can be represented by the supported client fields and
identified unambiguously by its public key for an allowed management action.
Some valid peer records, such as peers with subnet routes, remain verifiable
without being manageable.
**Runtime observation**:
A read-only measurement of the running interface and its peers, such as routes,
last handshake, or current traffic counters. Missing observations are unknown or
unavailable; they do not imply zero traffic or successful application.
**Reserved address**:
An address retained so that it cannot be assigned to another client while a
deleted peer may still exist on the running interface. It is released only
after the saved peer set is successfully verified as applied.
**First delivery**:
The one-time, password-free delivery of a newly created client's connection
file after Commit in the same creation session. A failed file write can be
retried in that session; later delivery requires the vault password. Until the
saved configuration is verified as applied, delivery does not establish VPN
access.
**Commit**:
Saving prepared changes without applying them to the running VPN.
@@ -23,5 +61,8 @@ _Avoid_: User, account
**Client alias**:
An administrator-friendly name for a client. A client is identified by its public key rather than its alias.
An existing client's creation date remains unknown unless it is present in its
managed metadata; an unknown date is not filled in with an estimated value.
**Anomalous client record**:
A record with only a server configuration entry or only a client connection file, without its corresponding counterpart. Records are matched by public key and are not automatically repaired when discovered.
+296 -4
View File
@@ -126,7 +126,17 @@ Each row is a delivery milestone, not a completion claim. Expand its record befo
| 14 | Restart verification for valid legacy peers | 13 | accepted |
| 15 | Diagnosable systemd restart and unloaded-unit resolution | 14 | accepted |
| 16 | Root systemd private peer restart transport | 15 | accepted |
| 17 | Adaptive client list, persistent details and clear states | 16 accepted; existing M7/M12 TUI | accepted || 18 | Readable, complete in-app help for implemented workflows | 17 | accepted |
| 17 | Adaptive client list, persistent details and clear states | 16 accepted; existing M7/M12 TUI | accepted |
| 18 | Readable, complete in-app help for implemented workflows | 17 | accepted |
| 19 | Refactoring contract, terminology and behavior map | 18 | accepted |
| 20 | Explicit vocabulary and named operation contexts | 19 | pending |
| 21 | Domain ownership of reservations and apply knowledge | 20 | pending |
| 22 | Transactional draft editing and client creation vertical slice | 21 | pending |
| 23 | Readable saved-state Commit and restore scenarios | 22 | pending |
| 24 | Session-owned first delivery and password reissue scenarios | 23 | pending |
| 25 | Explicit restart verification scenario | 24 | pending |
| 26 | Application read models and thin TUI command adapter | 25 | pending |
| 27 | Integrated refactoring acceptance and architecture handoff | 26 | pending |
### Milestone 1 — Rust foundation, settings, and domain states
@@ -3865,11 +3875,293 @@ A more detailed guide requires functional scrolling as well as rewritten text. K
Accepted for presentation and synthetic fixture scope after independent review, R1/R2 closure and pinned Rust gates. No manual terminal-emulator, Alpine, disposable-VM, live WireGuard or production result is claimed. Stop after M18; do not commit, publish, deploy, access production or resume VM work.
## Approved domain refactoring programme — milestones 19–27
Status: M19 accepted; M20–M27 are pending. The user approved the proposed direction and requested detailed milestones on 2026-10-29. This paragraph records that plan's origin; current task progress is in the milestone records and resume point below. M18 remains accepted. These milestones preserve the approved product contract and all existing acceptance limitations, including retired/unaccepted M9 and unverified real-system paths. They do not resume VM work.
### Shared execution contract for M19–M27
Read before each dispatch: AGENTS.md, docs/SPEC.md, CONTEXT.md, the three docs/adr/0001–0003 decisions, docs/PROJECT_LAYOUT.md, docs/DEVELOPMENT.md, docs/TESTING.md, docs/ENVIRONMENT.md, docs/DEPENDENCIES.md, docs/CODEX.md, docs/IMPLEMENTATION.md, docs/EXECUTION_RECORDS.md, this section, the active milestone and its predecessor's evidence. Read only relevant historical evidence/diagnostics when a concrete question requires it. The coordinator must use the configured model/effort prerequisites; no silent substitution. Implementer/reviewer/repairer are separate GPT-6-Luna/high agents. One writer at a time; coordinator owns PLANS.md, task boundaries, findings and acceptance. Each table row is a bounded dispatch, not a request to implement the whole programme. Supply exact owned files, relevant interfaces, prerequisites and expected checks to every agent. Inspect the current tree before dispatch because paths below describe planned destinations, not modules already present.
**Fixed constraints.** No product/UI workflow, shortcut, persisted schema/version, archive member, default path, cryptographic primitive, authentication policy, dependency, Cargo feature or transport behavior change. No new crates/workspace, generic repository framework, event sourcing, CQRS infrastructure, plugin system, speculative traits or arbitrary file-size targets. Concrete internal owners and narrow functions are preferred. Keep byte-preserving editing in wireguard, secure I/O and recovery in their adapters, and temporary secrets out of public projections/Debug/errors. Do not change Cargo.toml/Cargo.lock for this programme. If a correctness defect or requirement conflict appears, record it and isolate affected work; do not hide a behavior change in a move. Existing external API/test consumers must be inventoried before removing a public escape hatch. Retain a narrow forwarding API where needed during migration; remove it only after all actual callers have migrated.
**Target responsibilities.** Names are working names to finalize in M19. `ServerPeerRecord` means an authoritative document record, potentially malformed/ambiguous/inspection-only; it is not a second independently mutable registry. The existing `Client` remains a restricted management projection, not a universal representation of every WireGuard peer. `RuntimePeer` remains an observation. `DraftChanges` is a boundary for coordinated document/client/vault edits, not a pure-domain holder of filesystem or crypto operations. `AddressReservations` owns reservation rules. `ApplyStatus` describes present knowledge; historical verification and raw observations remain distinct. `FirstDeliverySession` owns creation-session eligibility and short-lived secrets. Pure rules belong in domain; use cases coordinate adapters. The server document remains authoritative.
**Preservation matrix (required throughout).** Check creation/rename/delete/discard/Commit; IPv4-only and dual-stack clients; duplicate aliases; metadata-free, subnet-route, missing-IPv4, malformed and duplicate-identity peers; import anomalies and mismatches; original file preservation; mandatory backup failure; partial install/recovery; restore cancellation/stale preview/settings path restrictions; first-delivery retry/overwrite/cleanup/publication uncertainty; password-per-client reissue; shared restart deadline, service submission uncertainty, complete-peer verification and durable publication failure; reservation release only after verified restart; reopening does not infer current application from historical success; responsive worker cancellation/shutdown, generation checks and explicit QR disclosure. Preserve successful behavior and honest failures, not just function signatures.
**Validation profiles.** `DOC` means inspect Markdown local links/anchors, register/status/resume consistency, task numbering and factual claims, then `git diff --check`. `RUST` means environment checks in docs/ENVIRONMENT.md, focused existing behavioral tests for owned responsibilities, followed by `cargo fmt --all -- --check`, `cargo clippy --locked --all-targets -- -D warnings`, `cargo test --locked`, `cargo build --locked --release`, and `git diff --check` on final integrated inputs. Commands assume the verified pinned-toolchain environment; do not use a different system Cargo silently. Expected outcome is exit 0; record actual counts/ignored tests rather than copying M18 results. Do not run full gates for every diagnostic edit. Use existing test adapters and synthetic fixtures; add tests only for uncovered behavioral boundaries, not private field names or forwarding methods. Existing tests may move with their owner, but do not weaken/delete assertions to make the refactor pass. Review final production paths independently of test results.
**Evidence and acceptance.** At each milestone, coordinator creates/updates docs/evidence/milestone-N.md with source identity including dirty inputs, commands and results, acceptance mapping, independent review and limitations. Create docs/diagnostics/milestone-N.md only when needed. Review findings use stable M<N>-R<N> IDs with location, impact, expected behavior and reproduction; repair and re-review before acceptance. No claims from commands not run. Root, production keys/configs/services and live VPN testing are forbidden. Alpine is separate opt-in evidence and never systemd evidence; unavailable prerequisites remain incomplete validation. A public item is not made private merely to satisfy a structural goal without checking binary, integration tests and container-driver callers. Update docs/PROJECT_LAYOUT.md when actual responsibilities move; keep CONTEXT.md a glossary, without module paths or implementation notes.
### Milestone 19 — Refactoring contract, terminology and behavior map
Status: accepted (2026-10-29T22:24:30+02:00). Prerequisite: M18 accepted; no code refactoring was performed in this milestone.
#### Outcome and scope
Provide a precise migration contract so subsequent workers can perform bounded changes without inventing domain semantics. Inspect current source and test callers; documentation only. Planned owned files: CONTEXT.md, docs/architecture/domain-refactoring.md (new, created for substantive content), docs/evidence/milestone-19.md. Coordinator alone updates PLANS.md. Do not rewrite SPEC/ADRs or historical evidence.
#### Tasks and implementation steps
| Task | Owner | State | Bounded deliverable |
| --- | --- | --- | --- |
| M19.1 | implementer `/root/m19_inventory` | complete | Inventoried authority, application/domain/TUI ownership, test callers and public escape hatches; no edits or tests |
| M19.2 | implementer `/root/m19_inventory` | complete | Added glossary and source-referenced architecture contract with transitions, ownership, caller audit and M20–M27 test map; no code changes |
| M19.3 | fresh reviewer `/root/m19_reviewer`; coordinator | complete | R1–R6 closed by independent re-review; integrated DOC passed; M19 accepted |
1. Trace src/domain.rs, src/app.rs, src/runtime.rs, src/wireguard/{document,edit}.rs, src/persistence.rs, src/backup.rs, src/vault{,/import,/source}.rs, src/delivery.rs and src/tui/{state,events,forms,terminal,render}.rs. Include startup.rs, lib.rs, container_driver.rs and tests as API consumers. Distinguish production lines from colocated tests; file length alone is not a reason to split.
2. In the architecture document, make a table: current type/function/field -> business meaning -> authoritative source -> proposed owner -> readers/writers -> existing tests -> migration milestone. Cover Application fields, ClientStates fields/mutators, display/projection logic, authorization contexts and worker generations. Use verified source references. Record public escape-hatch callers (`draft_mut`, `vault_draft_mut`, `add_client`, `open`) and whether they can be replaced safely.
3. Add glossary entries for saved state, applied state/verification, server peer record, manageable client, runtime observation, reserved address and first delivery. Preserve existing meanings and avoid introducing user/account identities. Explain that a malformed peer can lack a usable public key and must still be inspectable; a valid subnet-route peer can be restart-verifiable without being manageable. Unknown creation dates remain unknown. Implementation names belong in the architecture document only.
4. Write transition tables with trigger, preconditions, next state, side effects, failure/cancel outcome and existing proof. Cover draft lifecycle, reservation creation/release, current vs historical verification, first delivery stages (including publication uncertainty/manual cleanup), restore preview/confirmation and restart attempt identity. Distinguish cancellation before execution from a submitted service job that may still finish.
5. Agree concrete destination modules and dependency direction; prefer domain child modules, app child modules and an application-owned read model. Record incremental API compatibility/removal strategy, persisted-format compatibility fixtures, gaps needing focused tests, and responsibility-specific test commands discovered from actual test names. No empty modules or architecture-only traits.
#### Acceptance and validation
- [x] M19-A1: every proposed owner/transition maps to actual source and tests; unsupported peers and historical verification are explicitly covered, with the unprojectable-peer restore gap recorded for M23.
- [x] M19-A2: glossary is domain-only; architecture map specifies authority and prevents an independent client registry.
- [x] M19-A3: each later milestone has an unambiguous migration surface and existing behavioral baseline; no product changes or new dependency are implied.
- [x] M19-A4: separate reviewer checked actual source, R1–R6 closed; integrated DOC passed.
Review and repair: M19-R1 (P2, restore state) closed by corrected rewrite/history contract and re-review. M19-R2 (P2, historical M18 handoff) closed by plan clarification/re-review. M19-R3 (P2, unprojectable-peer restore reservations) closed by documenting current behavior, focused test gap and M23 repair under the existing requirement; re-review confirmed. M19-R4 (P3, programme/M17 handoff) closed by plan clarification/re-review. M19-R5 (P3, stale M19.3 task row) closed by correction and re-review. M19-R6 (P2, future task/test-map fidelity) closed after tuple arity and actual test targets were aligned and independently rechecked.
Evidence/handoff: [M19 evidence](docs/evidence/milestone-19.md) identifies the dirty source inputs, exact DOC checks, reviewer dispositions and limitations. The user confirmed the coordinator uses GPT-6-Sol/medium; workers were dispatched with GPT-6-Luna/high. M18 is accepted; M9 is retired/unaccepted and is not an M19 prerequisite. No Rust tests/build, Alpine or VM checks were required or run for this documentation-only milestone. Outcome: accepted for the migration contract; next action, only on a separate request, is M20.1.
### Milestone 20 — Explicit vocabulary and named operation contexts
Status: pending. Prerequisite: M19 accepted.
#### Outcome and scope
Remove name ambiguity and opaque tuples without moving business rules. Planned owners: src/domain.rs and optional domain child modules, src/runtime.rs, src/app.rs, src/tui/{events,state}.rs, actual callers/tests, docs/PROJECT_LAYOUT.md. Keep public forwarding names only where the M19 caller map justifies them.
| Task | Owner | State | Bounded deliverable |
| --- | --- | --- | --- |
| M20.1 | implementer | pending | Rename conflicting knowledge/observation types and migrate callers |
| M20.2 | implementer | pending | Replace prepared-operation tuples with private named structures |
| M20.3 | reviewer; repairer; coordinator | pending | Review semantic equivalence, repair, RUST acceptance |
1. Rename the domain `RuntimeObservation` (currently Unknown/Unavailable/Verified) to the M19 canonical knowledge name, such as `ApplyStatus`; retain runtime::RuntimeObservation for measured interface data. Decide whether tui::AppliedState is a necessary presentation mapping or an exact redundant copy. Remove only redundant representation; do not merge raw statistics and verification status. Inventory every `as Statistics`/`as TechnicalObservation` alias and change only aliases made unnecessary by clarified names.
2. Replace ApplicationExecutor.prepared_destination's six-element tuple with named fields for identity, QR choice, delivery mode, retry attempt, generation and PreparedDestination. Replace restore_preview's tuple similarly. Preserve secret ownership, clear-on-unrelated-command behavior, stale-generation refusal, overwrite confirmation and cancellation. Avoid adding Clone/Debug to secret-bearing types.
3. Introduce only justified value types from M19 (for example a typed delivery mode replacing `reissue: bool` inside the prepared context). Migrate constructors/pattern matches/callers atomically for each type; do not redesign public commands or retry flags in this milestone.
4. If splitting domain.rs, move coherent definitions/tests mechanically, preserve exports needed by callers and keep implementation separate from semantic edits. Inspect diff for changed branches/order/errors; those belong to later milestones or separately recorded defects.
#### Acceptance and validation
- [ ] M20-A1: present apply knowledge and measured observation have distinct canonical names; no accidental meaning change in UI labels.
- [ ] M20-A2: prepared contexts use named fields; generation/cancel/confirmation behavior and secret redaction remain equivalent.
- [ ] M20-A3: actual consumers compile, dependencies/formats untouched; focused worker/state tests and RUST pass after independent review.
Focused starting commands: `cargo test --locked domain::`, `cargo test --locked runtime::`, `cargo test --locked tui::events::`, `cargo test --locked tui::state::`; adjust filters only to actual moved modules and report executed tests. Evidence: docs/evidence/milestone-20.md. Outcome: not accepted. Handoff: M21 only after acceptance.
### Milestone 21 — Domain ownership of reservations and apply knowledge
Status: pending. Prerequisite: M20 accepted.
#### Outcome and scope
Encapsulate address retention and verification transitions without changing persistence or technical matching. Planned production owners: src/domain.rs, src/domain/{reservations,apply}.rs as needed, app.rs and persistence.rs caller migration. Runtime matching stays in runtime; serialization stays in persistence. Creation-session secret state is out of scope.
| Task | Owner | State | Bounded deliverable |
| --- | --- | --- | --- |
| M21.1 | implementer | pending | Encapsulated reservation rules and existing allocator integration |
| M21.2 | implementer | pending | Explicit current/historical verification transitions and caller migration |
| M21.3 | reviewer; repairer; coordinator | pending | Review invariant matrix, repair and RUST acceptance |
1. Extract `AddressReservations` with private storage and intention-revealing operations to retain removed host addresses, restore conservative persisted reservations and expose read-only iteration/availability. Preserve conflict rejection, draft unsupported-peer reservations, Commit timing, discard behavior, IPv4/IPv6 allocation and broad-route deletion restrictions. Do not enumerate networks or infer free addresses from unavailable runtime data.
2. Keep draft-only pending reservations distinct from committed reservations until publication. Follow M19's transition table; preserve reservations from unprojectable exact host routes, and reject unsafe broad-route deletion as before. Move policy, not filesystem/parsing/crypto, into domain.
3. Separate current apply knowledge, optional last applied management snapshot and historical verified timestamp/projection. Preserve the valid state in which complete technical matching is verified but no complete management Snapshot can represent all peers. A single enum must not force a fake/partial snapshot.
4. Replace scattered field assignments with narrow transitions: saved state changed; runtime knowledge lost/unavailable; verified result durably published. Technical comparison success alone must not clear reservations or publish applied state before required internal-state persistence succeeds. Keep attempt authorization/checks private to the restart scenario until M25; do not expose an unrestricted public `mark_applied` method.
5. Migrate PersistedState conversion without changing TOML fields/version/order semantics or archive compatibility. Round-trip pre-refactor synthetic state, including reservations and historical timestamp. Reopen must restore conservative reservations and Unknown current knowledge. Keep existing failure paths intact.
#### Acceptance and validation
- [ ] M21-A1: only named domain transitions can release committed reservations; backup/service/read/mismatch/persistence failures cannot release them.
- [ ] M21-A2: legacy technical verification without a management snapshot, reopening and current vs historical status retain prior semantics.
- [ ] M21-A3: allocator, reservation, persisted round-trip and app failure tests pass; independent invariant review and RUST pass.
Focused starting commands: `cargo test --locked domain::`, `cargo test --locked persistence::`, `cargo test --locked app::`; reviewer specifically inspects reservation-release callers, not only tests. Evidence: docs/evidence/milestone-21.md. Outcome: not accepted. Handoff: M22 after acceptance.
### Milestone 22 — Transactional draft editing and client creation vertical slice
Status: pending. Prerequisite: M21 accepted.
#### Outcome and scope
Give document/client/vault drafts one coordinated publication boundary and make create -> preview -> Commit readable end to end. Planned owners: src/app.rs, src/app/{draft,clients,import}.rs as warranted, necessary domain APIs, startup/test callers and focused tests. Preserve app::Application public entry points during extraction. Commit internals are refactored in M23; delivery internals in M24.
| Task | Owner | State | Bounded deliverable |
| --- | --- | --- | --- |
| M22.1 | implementer | pending | Internal draft owner and staged replacement API |
| M22.2 | implementer | pending | Create/rename/delete/discard and setup/import migration |
| M22.3 | reviewer; repairer; coordinator | pending | Vertical-slice/failure review, repair and RUST acceptance |
1. Build an internal `DraftChanges` owner containing the lossless document, restricted client projection, encrypted vault draft and draft-only reservation changes, or the concrete composition agreed in M19. It belongs to the application boundary because it coordinates crypto/document components. Avoid deep-cloning plaintext keys and avoid generic transactions/closures that obscure what is staged.
2. For each edit, prepare all fallible replacements locally, validate the edited document and corresponding public projection, then replace live draft components together. No file/runtime mutation is allowed. Document is authoritative: never rebuild it from Snapshot, and do not drop malformed/unsupported records merely because projection fails.
3. Migrate prepare_new_client first: resolve settings as currently required, calculate occupancy from full document plus reservations/server addresses, validate manual dual-stack addresses or allocate defaults, generate key, encrypt record, stage document/client/vault updates, and register session-first-delivery material only on successful publication. Preserve timestamp validation and key/error redaction. Keep first-delivery ownership temporarily behind its existing interface for M24.
4. Migrate add_client/rename_client/delete_client/discard_draft one at a time. Preserve alias validation/duplicates, metadata preservation, fallback labels, deletion restrictions, retained-address policy and precise revocation of session issue rights. Cancel/discard must restore saved data without writes or recreating consumed first-delivery eligibility.
5. Migrate initial vault setup and selected-file import through the same draft publication boundary; preserve all-or-nothing staging, key matching, anomaly reports and source-file preservation. Keep source scanning/reconciliation and encryption in existing adapters. Restrict `draft_mut`/`vault_draft_mut` only after replacing all real callers with intentional operations; tests must not be granted a production bypass.
6. Keep Application a facade delegating to cohesive owners; avoid moving the same large impl into a differently named catch-all file. Add focused failure tests only where no existing test proves that an error leaves every draft component and first-delivery eligibility unchanged. Update actual layout documentation.
#### Acceptance and validation
- [ ] M22-A1: create/rename/delete/import/setup/discard use coordinated draft publication; intermediate errors leave no partial draft or newly authorized issue.
- [ ] M22-A2: source bytes outside allowed edits, existing client parameters, unsupported records and original import files remain preserved.
- [ ] M22-A3: synthetic create -> preview -> Commit -> first delivery -> explicit fixture restart and deletion/discard flows still pass; no live runtime operation occurs during draft/Commit.
- [ ] M22-A4: independent review checks authority/secret lifetimes; RUST passes with unchanged formats/dependencies.
Focused commands: `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`. Evidence: docs/evidence/milestone-22.md. Outcome: not accepted. Handoff: M23 after acceptance.
### Milestone 23 — Readable saved-state Commit and restore scenarios
Status: pending. Prerequisite: M22 accepted.
#### Outcome and scope
Extract explicit saved-state scenarios with visible ordering and honest failure publication. Planned owners: src/app.rs, src/app/{commit,restore,backups}.rs, facade/private session interfaces, affected tests. Persistence journal, archive format, path security and low-level I/O algorithms stay in persistence.rs/backup.rs; only necessary call-site adjustments there.
| Task | Owner | State | Bounded deliverable |
| --- | --- | --- | --- |
| M23.1 | implementer | pending | Commit preparation, backup/install, in-memory publication |
| M23.2 | implementer | pending | Backup listing/creation and preview-confirm restore scenario |
| M23.3 | reviewer; repairer; coordinator | pending | Ordering/recovery/compatibility review, repair and RUST acceptance |
1. Extract Commit as readable preparation -> validate proposed saved bytes -> completed pre-Commit backup of installed state -> journaled install -> publish new session state. Keep a concrete prepared result if it clarifies which fallible work precedes mutation. Do not turn backup confirmation into a public forgeable permission or allow callers to skip it.
2. Preserve failure behavior: draft remains retryable, readback after failed install conservatively retains/revokes first-issue rights, saved/application history is not advanced on failure, and recovery on reopen follows the existing journal. Avoid an apparently infallible final publication that hides parsing or state-install errors.
3. Extract preview_restore and confirm_restore. Preview remains read-only and secret-safe; confirmation validates the bound saved generation/installation paths, creates precautionary backup and fully replaces the saved components (including absent optional components). Preserve draft warning/cancel and settings re-resolution. Restore never requests a vault password or touches the running interface.
4. Move manual/pre-restart backup entry points only as needed to establish a clear backup responsibility. Historical verification labels must refer to the archived generation, not the newest archive or current statistics. Keep cloned SavedBytes secret-bearing and out of ordinary read models/Debug.
5. Keep service/runtime dependencies unavailable to Commit and restore modules except shared public status types when necessary; reviewer must inspect actual dependency/call paths. Preserve existing callback failure seams with concrete narrow interfaces; do not add a general storage/service framework.
#### Acceptance and validation
- [ ] M23-A1: completed backup precedes protected mutation for Commit/restore; failures and cancel retain documented draft/saved/runtime behavior.
- [ ] M23-A2: restore compatibility, path restrictions, absent components, recovery and historical status tests retain their assertions.
- [ ] M23-A3: fixture old-state backups and state/vault/archive compatibility checks pass; no runtime/service call in saved-only paths; independent review and RUST pass.
Focused commands: `cargo test --locked app::`, `cargo test --locked persistence::`, `cargo test --locked backup::`, `cargo test --locked --test commit_restore`, `cargo test --locked --test client_delivery`. Evidence: docs/evidence/milestone-23.md. Outcome: not accepted. Handoff: M24 after acceptance.
### Milestone 24 — Session-owned first delivery and password reissue scenarios
Status: pending. Prerequisite: M23 accepted.
#### Outcome and scope
Isolate the session-first-delivery state machine and expose readable authenticated delivery scenarios. Planned owners: src/app.rs, src/app/{first_delivery,reissue}.rs or cohesive delivery children; affected facade/TUI adapter callers and tests. Cryptography, secure publication and QR encoding remain in vault/delivery adapters. No workflow or password policy change.
| Task | Owner | State | Bounded deliverable |
| --- | --- | --- | --- |
| M24.1 | implementer | pending | FirstDeliverySession eligibility, invalidation and retry transitions |
| M24.2 | implementer | pending | First-file/QR and password-reissue scenario extraction |
| M24.3 | reviewer; repairer; coordinator | pending | Secret/authorization/failure review, repair and RUST acceptance |
1. Move InitialIssue/IssueStage/IssueContext/token bookkeeping and matching/promote/retain helpers under one private owner. Keep ephemeral private keys in zeroizing/secret wrappers with no ordinary Clone/Debug/serialization. Use M19's transition table rather than simplifying PendingDraft/AwaitingFirstFile/RetryAvailable/Authorizing/overwrite/manual-cleanup states into one bool.
2. Expose narrow internal transitions for draft publication, successful Commit, discard/delete, changed peer/encrypted record, begin attempt and completed/failed publication. Tokens remain single-use, session-local, bound to client and saved context; no caller can manufacture a first-issue authorization. Preserve unrelated clients' rights when rename/restart does not invalidate their context.
3. Keep final saved-state authorization check adjacent to secure file publication with exclusive session ownership across the boundary. Extracting a component must not allow mutations between checking authorization and publishing a secret. Preserve retry to another directory and explicit overwrite/cleanup acknowledgements after uncertain failures.
4. Extract password reissue separately: selected client + password for this action -> decrypt/validate -> render -> mandatory file publication -> optional QR from the same payload. Never cache an unlocked vault or fall back to original plaintext files. Preserve imported parameters, discrepancy blockers and authoritative server values.
5. Preserve independent outcomes: file publication may succeed while QR fails; file may exist while bookkeeping is uncertain; warnings about not-yet-verified activation must survive failed/uncertain publication. Only successful mandatory publication consumes eligibility according to existing conservative uncertainty rules. Do not remove existing detailed failure variants or replace them with success/failure bools.
#### Acceptance and validation
- [ ] M24-A1: all first-delivery transitions/invalidation rules have one owner; pre-Commit, stale, consumed and reopened sessions cannot deliver without authorization.
- [ ] M24-A2: retry/uncertain publication/manual cleanup/bookkeeping failure and same-payload QR behavior remain covered; passwords and secrets absent from errors/Debug/projections.
- [ ] M24-A3: reissue asks per client/action and respects import mismatch policy; independent security-relevant path review and RUST pass.
Focused commands: `cargo test --locked app::`, `cargo test --locked delivery::`, `cargo test --locked vault::`, `cargo test --locked --test client_delivery`, `cargo test --locked --test tui_workflow`. Evidence: docs/evidence/milestone-24.md. Outcome: not accepted. Handoff: M25 after acceptance.
### Milestone 25 — Explicit restart verification scenario
Status: pending. Prerequisite: M24 accepted.
#### Outcome and scope
Make restart orchestration independently readable while preserving complete technical verification and every uncertainty boundary. Planned owners: src/app.rs, src/app/restart.rs and optional private restart children, facade/state transition interfaces, affected tests/container-driver imports. Netlink and systemd transports/decoders remain unchanged.
| Task | Owner | State | Bounded deliverable |
| --- | --- | --- | --- |
| M25.1 | implementer | pending | Restart preview, bound attempt and preparation extraction |
| M25.2 | implementer | pending | Ordered job/observation/verification/durable publication scenario |
| M25.3 | reviewer; repairer; coordinator | pending | Failure/deadline/legacy-peer review, repair and RUST acceptance |
1. Move preview/report/choice/diagnostic types and restart implementation coherently. Preserve current public imports with re-exports until callers migrate. Preview validates unsafe SaveConfig, structural ambiguity and complete SavedPeerProjection; it must not require every saved peer to fit Client/Snapshot.
2. Read the scenario in this order: validate preview against saved state -> honor cancel/draft choice -> complete backup -> discard draft at existing safe point -> submit configured service restart -> successful job completion -> bounded interface observation -> compare complete saved peer set -> durable verification bookkeeping -> publish applied knowledge/release reservations. Preserve actual existing ordering where this outline omits preparation; document it instead of introducing behavior changes.
3. Keep one shared monotonic deadline, existing clock/failure injection, restart attempt identity, generation checks and stale/duplicate-result rejection. An accepted job request is not a completed job. Timeout/submitted/may-still-complete/manual-recovery reporting must not collapse into one generic error.
4. Keep complete-peer technical projection/comparison in its existing responsibility; management metadata/aliases must not become technical match requirements. Preserve metadata-free, IPv4-only, subnet-route and missing-IPv4 valid peers, malformed/duplicate identities, incomplete dumps and peer mismatch behavior. Current statistics must not assert verification.
5. Persistence failure after successful matching must retain conservative apply knowledge/reservations and honest report. Do not introduce rollback or a second restart. Fixture apply callback must remain cfg(test)-only and unreachable from the production executable. Review both real transport call paths for unchanged use, without executing host services.
#### Acceptance and validation
- [ ] M25-A1: backup/job/read/verify/persist/publication sequence and cancellation semantics unchanged; current/saved generations and attempt identity remain bound.
- [ ] M25-A2: full-peer legacy cases, shared deadlines, service/transport distinctions, mismatch and post-verification persistence failure tests pass; reservations release only after durable verified success.
- [ ] M25-A3: no production fixture bypass, rollback or transport change; independent review and RUST pass. Real-system/IPv6 limits remain explicit.
Focused commands: `cargo test --locked app::`, `cargo test --locked runtime::`, `cargo test --locked service::`, `cargo test --locked --test commit_restore`, `cargo test --locked --test runtime_restart`. Run only synthetic non-ignored container-driver tests in ordinary gates. Evidence: docs/evidence/milestone-25.md. Outcome: not accepted. Handoff: M26 after acceptance.
### Milestone 26 — Application read models and thin TUI command adapter
Status: pending. Prerequisite: M25 accepted.
#### Outcome and scope
TUI consumes public application facts and action eligibility without parsing saved configuration or owning authorization rules. Planned owners: src/app/read_model.rs, app facade, src/tui/{state,events,forms,terminal,render}.rs and new cohesive TUI children, focused tests. Preserve output wording/geometry/keys/help, fixed footer and worker lifecycle.
| Task | Owner | State | Bounded deliverable |
| --- | --- | --- | --- |
| M26.1 | implementer | pending | Secret-free application view and per-action eligibility |
| M26.2 | implementer | pending | Migrate TUI projection and command execution adapter |
| M26.3 | implementer | pending | Split command/result/navigation responsibilities where justified |
| M26.4 | reviewer; repairer; coordinator | pending | UI parity/authorization/lifecycle review, repair and RUST acceptance |
1. Move ClientDisplay/SessionDisplay projection responsibilities to an application-owned read model. Include saved peer count, draft/saved public client differences, issue classification, reservation/apply facts and vault-initialized state. Keep UI-only selection, scroll, dialog focus, rendered strings/styles and worker generation in TUI. Raw ConfigDocument/SavedBytes/vault/password/payload must not be fields of the public view.
2. Represent action eligibility explicitly per action with typed reasons where useful. Audit current guards first: rename, delete, first delivery and restart do not necessarily have identical eligibility. In particular inspection-only management projection must not accidentally prohibit an already-supported deletion by public key. Never silently enable actions for malformed/ambiguous peers. Eligibility is guidance for presentation; application execution revalidates its actual preconditions.
3. Replace SessionView::from_application reparsing of saved bytes and recovery of business facts with the application view. Preserve unsupported peers without public keys, duplicate-identity records, removed-draft rows, selected public identity and safe peer-index inspection. Application view does not infer applied from handshake/counters or historical timestamp.
4. Extract ApplicationExecutor/prepared contexts/result adaptation from worker transport into a focused command adapter if it improves ownership. Keep the bounded single-owner worker, statistics generation filtering, prepared-preview/destination lifetime, cancellation, duplicate-submit protection, shutdown and terminal cleanup unchanged. No independent executor may share mutable Application or plaintext secrets.
5. Separate terminal lifecycle from key navigation/result messages only after the application boundary is clear. Split render.rs into cohesive list/details/help/footer modules if their responsibilities warrant it; preserve existing metrics, wrapping, QR disclosure and footer packing. Avoid generic dispatch registries or purely mechanical many-file splits. Each subtask must stay within one bounded responsibility and compile before the next begins.
6. Update internal help/user docs only for factual architecture references; no product wording/shortcut redesign. Keep masks and secret forms short-lived. Compare existing synthetic buffers and PTY workflows across established terminal dimensions, including 34x8, 40x9, 60x16, 80x24, 100x30 and 180x50.
#### Acceptance and validation
- [ ] M26-A1: TUI no longer parses saved server bytes to obtain application facts; read model is secret-free and includes all authoritative records.
- [ ] M26-A2: per-action availability matches existing handlers, with authoritative revalidation; selection/cancel/draft/QR behavior preserved.
- [ ] M26-A3: worker generation/continuation/shutdown tests, responsive help/list/footer buffer tests and synthetic PTY workflows pass without weakened assertions.
- [ ] M26-A4: reviewer checks UI parity and boundary direction; RUST passes; actual module layout documented.
Focused commands: `cargo test --locked tui::`, `cargo test --locked --test tui_workflow`, `cargo test --locked --test client_delivery`. Evidence: docs/evidence/milestone-26.md. Outcome: not accepted. Handoff: M27 after acceptance.
### Milestone 27 — Integrated refactoring acceptance and architecture handoff
Status: pending. Prerequisite: M26 accepted.
#### Outcome and scope
Verify the integrated refactoring against the original behavior map and demonstrate that common operations can be understood from their owning scenario. Owned scope: actual migrated APIs/tests, docs/architecture/domain-refactoring.md, CONTEXT.md, docs/PROJECT_LAYOUT.md and evidence. No new features or broad cleanup. Repairs must have named findings and bounded ownership.
| Task | Owner | State | Bounded deliverable |
| --- | --- | --- | --- |
| M27.1 | implementer | pending | Remove proven-obsolete compatibility paths and reconcile architecture map |
| M27.2 | independent reviewer | pending | Integrated behavior/authority/security/readability audit |
| M27.3 | repairer; reviewer; coordinator | pending | Bounded repairs, final integrated gates and handoff |
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.
3. Independent reviewer audits the full preservation matrix and baseline-to-final diff, not only the latest milestone. Confirm persisted schemas/versions, archive members, cryptography/dependencies/defaults, public-key identity, byte-preserving authority, imported originals and short-lived secrets. Check for redundant mutable state and accidental widening of pub visibility. Existing fixtures from before the refactor must still load with unchanged meaning.
4. Run focused integrated tests and final RUST on identified final inputs. Establish which M8 Alpine evidence was invalidated by changed application inputs. If an explicitly authorized disposable Alpine environment is available, rebuild the current driver and follow docs/TESTING.md; otherwise record Alpine revalidation as incomplete, not a host-gate pass. No VM work is included, no systemd/IPv6 verification is inferred, and no release-readiness claim is authorized by this programme.
5. Accept the refactoring only for its documented host/synthetic scope after all required RUST checks and independent findings close. Report Alpine state separately; if any task explicitly made an Alpine check mandatory, keep that task incomplete until run. Final plan/evidence must state real-system limitations and identify the tested source. Stop after M27; do not commit/publish/deploy or launch another programme.
#### 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.
Evidence: docs/evidence/milestone-27.md. Outcome: not accepted. Handoff: stop after scoped acceptance; no subsequent milestone is authorized by this plan.
## Current progress and resume point
- M18 is accepted (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. Stop here; no next milestone is started.
- M19 is accepted (2026-10-29T22:24:30+02:00): glossary and source-referenced migration contract passed independent R1–R6 closure and integrated DOC checks; [M19 evidence](docs/evidence/milestone-19.md) records inputs and limits. Stop after M19; M20–M27 remain pending. M18 remains accepted; M9 remains retired and real-system limitations remain unchanged.
- M17 is accepted (2026-10-28): adaptive columns, persistent selected-client details and explicit state/count presentation passed independent R1/R2 closure, synthetic/PTY checks and pinned Rust completion gates. [M17 evidence](docs/evidence/milestone-17.md) records final inputs and limits. Stop here; no next milestone is started.
- 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.
- M17 was accepted on 2026-10-28: adaptive columns, persistent selected-client details and explicit state/count presentation passed independent R1/R2 closure, synthetic/PTY checks and pinned Rust completion gates. [M17 evidence](docs/evidence/milestone-17.md) records final inputs and limits. At that handoff, M18 had not started.
- TUI-FL-001 is complete (2026-10-27): fixed-height two-row keyboard footer, narrow Help/overflow behavior and compact nine-row handling passed synthetic rendering tests, independent R1–R3 closure and pinned Rust gates. [Footer evidence](docs/evidence/tui-footer-layout.md) identifies source/artifact inputs.
- Milestone 16 is accepted by explicit user direction following a reported successful manual check. Earlier independent review/repair and pinned Rust gates passed; private-peer and both real transport paths still lack recorded disposable-VM evidence. [M16 evidence](docs/evidence/milestone-16.md) records the source and artifact. No production result is claimed by this execution.
@@ -3894,7 +4186,7 @@ Accepted for presentation and synthetic fixture scope after independent review,
- 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.
- Next action: none for M18. Stop after acceptance. Keep M16 validation limitations separate from TUI evidence. Do not access production, commit, deploy or resume VM work.
- M18 handoff remains complete. M19 is accepted; the next implementation action, when separately requested, is M20.1. Keep M16 validation limitations separate from TUI evidence. Do not access production, commit, deploy or resume VM work.
## Plan maintenance
+220
View File
@@ -0,0 +1,220 @@
# Domain and application refactoring contract
Status: migration contract for M20–M27. This document describes the current
implementation and the agreed destination boundaries; it does not authorize
behavior, file-format, dependency, or product changes. M19 completes only after
independent review and DOC checks.
## Authority and boundaries
The server configuration is authoritative for server peers and their managed
descriptive metadata. `ConfigDocument` preserves its bytes and exposes every
peer section, even when a section has no usable public key or cannot be
represented by the supported client model. `ClientStates` is a restricted
projection used for client operations; it is not a second peer registry. The
vault is authoritative for encrypted client secrets, and the saved files are
authoritative for persisted state. Runtime reads are observations, not desired
configuration. Historical successful verification is not current verification.
The dependency direction remains:
```text
TUI -> application facade / application read model -> domain rules
| -> config document/editor
| -> vault/import/delivery
| -> backup/persistence
| -> runtime/service adapters
```
Domain rules do not read files, parse server configuration, perform cryptography,
call the runtime/service, or render terminal text. The application coordinates
these owners. The TUI submits typed requests and displays secret-free facts; it
does not authorize business operations or parse saved configuration. The
document editor never rebuilds the server file from a client snapshot.
## Current concepts and migration map
References use repository-relative paths and line numbers from the M19.1 source
inspection. Test names identify existing synthetic behavioral coverage; no test
is claimed to have been rerun for this document.
### Application session fields and workflows
| Current type / field / function | Business meaning | Authority | Proposed owner | Readers / writers | Existing proof | Migration |
| --- | --- | --- | --- | --- | --- | --- |
| `Application::store` (`src/app.rs:81-94`) | Saved-file boundary, configured paths, validation and install capability | `SavedStore` paths and installed files | Application facade/session, then `app/commit.rs`, `app/restore.rs`, `app/backups.rs` workflow owners | All app operations read; commit/restore update installed files | `commit_restore::backup_failure_blocks_commit_and_restore_without_losing_drafts_or_saved_bytes`; persistence `complete_install_publishes_components_with_private_modes` | M23 |
| `Application::saved` | Exact installed generation held by this session | `SavedBytes` read from `SavedStore` | Application session; saved-operation contexts in M23/M25 | Open/read, commit, restore, verified bookkeeping, restart preview | `app::commit_advances_saved_state_without_applying_and_backup_contains_old_bytes`; `app::successful_restore_clears_verified_current_history_but_backup_retains_it` | M23, M25 |
| `Application::document` | Lossless current server-configuration draft and peer authority | Original/current saved server bytes parsed as `ConfigDocument` | `app/draft.rs` draft owner (M22), with editing rules in `wireguard/edit.rs` | Application reads; create/rename/delete/restore replace it; Commit serializes it | `app::new_client_preparation_is_transactional_and_uncommitted_keys_cannot_issue`; `config_preservation::edits_keep_interface_and_unowned_bytes_exact_and_can_be_reparsed` | M22 |
| `Application::states` | Narrow draft/saved management snapshots, apply knowledge, reservations | Document projection for manageable clients plus persisted internal state | Domain `ClientStates`, optionally `domain/apply.rs` and `domain/reservations.rs` | App edits/Commit/restart/reopen; app and TUI currently read | Domain tests `discard_restores_draft_and_commit_keeps_applied_snapshot_separate`, `successful_verified_apply_releases_deleted_addresses_and_mismatch_does_not`; `persistence::persisted_history_never_marks_the_runtime_verified` | M20, M21, M22 |
| `Application::vault` | Encrypted vault draft used for secret encryption, import and reissue | Installed vault bytes at open, then current app draft | `app/draft.rs` coordinates; cryptographic/value operations stay in `vault.rs` | App setup/import/create/delete/Commit/reissue read or update it | `app::selected_file_import_uses_the_existing_vault_draft_without_saving`; `app::deletion_only_removes_selected_current_record_and_retains_other_records`; `vault::encrypted_bundle_round_trips_without_unlocking_and_retrieves_with_password` | M22, M24 |
| `draft_unsupported_reservations` | Draft-only exact host addresses retained when a saved peer cannot project to `Client` | Saved document peer routes and pending deletion | Domain reservation policy, staged by application draft owner | Delete adds; Commit persists/promotes; discard clears | `app::deletion_of_unprojectable_peer_persists_its_exact_host_route_reservation`; `app::deletion_of_unprojectable_peer_with_broad_route_fails_without_draft_change` | M21, M22 |
| `historical_apply` | Last successfully persisted verification timestamp/status, for archive/history display | `PersistedState.last_verified_at_utc` and archive manifest | Persistence codec owns representation; application read model presents it | Open/backup/restore/restart bookkeeping | `persistence::persisted_history_never_marks_the_runtime_verified`; `app::verified_history_persists_across_reopen_without_marking_runtime_applied`; `app::historical_verified_timestamp_does_not_suppress_reissue_warning_after_reopen` | M21, M23, M26 |
| `verified_saved_projection` | Saved generation verified by the current session, used for delivery activation warning | Successful full runtime comparison bound to saved bytes | Application session/restart scenario; read-only view exposes only safe status | Restart publication writes; delivery warning reads; edits/restore invalidate as appropriate | `app::first_issue_warning_tracks_verified_saved_generation_for_file_and_qr`; `app::historical_verified_timestamp_does_not_suppress_reissue_warning_after_reopen` | M21, M24, M25, M26 |
| `active_restart_attempt`, `next_restart_attempt` | One-use authorization that binds verification publication to the confirmed attempt and saved generation | Application-created attempt ID plus saved-byte equality | Private `app/restart.rs` scenario context | Preview/confirm/restart lifecycle; verified persistence consumes it | `app::restart_confirmation_rejects_a_generation_changed_after_preview`; `app::restart_mismatch_and_post_restart_write_failure_never_release_addresses` | M20, M25 |
| `initial_issues`, `next_issue_authorization` | Creation-session-only first-delivery eligibility, one-use authorization and retry state | Generated key held in memory, committed peer directives and encrypted record binding | `app/delivery.rs` session owner; `vault.rs` remains cryptographic authority | Create registers after coordinated draft staging; Commit promotes; delivery consumes or updates; delete/discard/invalidation removes | `app::first_issue_authorization_is_single_flight_and_success_consumes_it`; `app::deletion_and_discard_revoke_only_session_bound_initial_material`; `app::uncertain_first_file_publication_retains_warning_and_skips_qr` | M20, M22, M24 |
| `open`, `open_from_saved_store`, `open_saved` (`src/app.rs:493-545`) | Construct a session from supplied state or derive supported state from saved document; reopen is Unknown now, regardless of history | Installed files/document; persisted reservations/history | Application session constructor | Production startup uses `open_from_saved_store`; `open` is used by tests | `startup::management_session_loads_supported_state_and_keeps_unsupported_peers_visible`; `app::verified_history_persists_across_reopen_without_marking_runtime_applied` | M22/M27 compatibility review |
| `prepare_new_client`, `add_client`, `rename_client`, `delete_client`, `discard_draft` (`src/app.rs:603-705,1253-1385`) | Stage all client/document/vault changes without saved-file or runtime effects | Document plus restricted client projection and vault | `app/draft.rs` and cohesive `app/clients.rs` operations, calling domain/editor/vault owners | UI uses prepare/rename/delete/discard commands; container test driver uses prepared flow; `add_client` has test callers only | `app::new_client_preparation_is_transactional_and_uncommitted_keys_cannot_issue`; `client_delivery::committed_creation_retries_after_overwrite_decline_and_duplicate_aliases_are_independent`; `client_delivery::deletion_and_restore_remove_password_free_authority_and_reopen_requires_vault_password` | M22 |
| `commit`, `preview_restore`, `confirm_restore` (`src/app.rs:1853-1965,2041-2147`) | Commit atomically installs staged saved bytes after backup; restore is a preview-bound full saved-state replacement after backup | App draft for Commit; validated archive payload for restore | `app/commit.rs`, `app/restore.rs`; low-level formats/install remain in `backup.rs`/`persistence.rs` | TUI executor calls facade; store installs only with `BackupComplete` | `app::commit_advances_saved_state_without_applying_and_backup_contains_old_bytes`; `app::preview_is_read_only_and_confirm_restores_without_unlocking_vault`; `commit_restore::backup_failure_blocks_commit_and_restore_without_losing_drafts_or_saved_bytes` | M23 |
| `preview_restart`, `restart`, `mark_verified_with_attempt` (`src/app.rs:1422-1530,1967-2038`) | Confirmed restart scenario: preconditions, backup, service job completion, runtime comparison, durable status then publish applied knowledge | Saved generation + complete `SavedPeerProjection` + runtime observation | `app/restart.rs`; service submission stays `service.rs`; comparison stays `runtime.rs`; durable fields stay persistence | TUI previews/confirms; fixture callback is test-only; success updates state only after durable write | `app::confirmed_restart_orders_backup_job_read_and_durable_publication`; `app::service_timeout_reports_submission_and_may_still_complete`; `app::failed_verified_history_write_does_not_advance_memory_or_reopen_as_verified` | M20, M21, M25 |
### Domain state and transitions
| Current type / field / operation | Business meaning | Authority | Proposed owner | Readers / writers | Existing proof | Migration |
| --- | --- | --- | --- | --- | --- | --- |
| `Client` / `ClientAddresses` / `Snapshot` (`src/domain.rs:60-147`) | Public, manageable projection keyed by public key; aliases may repeat, IPv6 may be absent for legacy clients | Public key and supported host-address projection from document | Domain client/value modules | App operations and read model; draft/saved snapshot transitions | `domain::aliases_may_repeat_but_control_characters_are_rejected`; `domain::existing_ipv4_only_clients_remain_ipv4_only`; `wireguard::wireguard_projection_supports_ipv4_only_and_rejects_ipv6_only_or_default_routes` | M20/M21 mechanical module move only if useful |
| `ClientStates::draft` / `saved` (`src/domain.rs:159-165`) | Manageable-client snapshots for staged and committed state, not all server peers | Document is source; snapshots are derived | Domain client states, mutated only through narrow transitions | Application edits draft, Commit advances saved; TUI currently reads both | `domain::discard_restores_draft_and_commit_keeps_applied_snapshot_separate`; `app::commit_advances_saved_state_without_applying_and_backup_contains_old_bytes` | M21/M22 |
| `applied_snapshot` / domain `RuntimeObservation` | Optional management snapshot last verified plus current knowledge enum; may be `None` while full technical verification is Verified for valid unmanageable peers | Current verified runtime comparison; historical timestamp is separate | Domain apply knowledge; technical evidence remains runtime-owned | Restart publication changes; reopen resets current knowledge; read model maps current knowledge | `app::restart_verifies_metadata_free_legacy_routes_without_partial_applied_snapshot`; `app::verified_history_persists_across_reopen_without_marking_runtime_applied` | M20 naming; M21 transitions |
| `reserved_addresses` and `restore_persisted_reservations` (`src/domain.rs:164,210-231`) | Persisted conservative addresses that cannot be allocated | Prior saved peer routes and internal state | `domain/reservations.rs` value/rules; `persistence.rs` only encodes/decodes | Allocator reads; Commit/reopen/restart update; restart success may release | `domain::a_draft_only_deletion_does_not_create_a_committed_reservation`; `domain::successful_verified_apply_releases_deleted_addresses_and_mismatch_does_not`; `persistence::state_codec_is_canonical_and_rejects_bad_or_unsupported_state` | M21 |
| `draft_mut` / `commit_draft` / `discard_draft` (`src/domain.rs:233-256`) | Current unrestricted draft mutation and snapshot transitions | `ClientStates` snapshots, ultimately document-derived | Replace with narrow domain operations in M21/M22; no public unrestricted mutator | Production `draft_mut` callers are only inside `Application`; direct outside-app callers are colocated domain tests | Domain tests above; app tests `discard_restores_draft_without_writing_installed_bytes`, `commit_failure_keeps_pending_issue_and_successful_retry_authorizes_it` | M21/M22; visibility removal after caller migration |
| `mark_applied_after_verification`, `mark_technically_applied_after_verification`, runtime unknown/unavailable (`src/domain.rs:258-296`) | Change present apply knowledge only after verified result publication; preserve current uncertainty and optional management projection | Runtime comparison result plus successful persisted bookkeeping | Domain apply transitions called only by application restart scenario | App restart success/failure/reopen; periodic TUI statistics must not call them | `domain::successful_verified_apply_releases_deleted_addresses_and_mismatch_does_not`; `app::restart_mismatch_and_post_restart_write_failure_never_release_addresses`; `tui::statistics_refresh_is_generation_bound_and_never_changes_applied_state` | M20/M21 |
| `allocate_ipv4`, `allocate_ipv6`, `AddressAvailability` (`src/domain.rs:341-464`) | Pure address allocation against server, occupied and reserved sets | Validated settings and document-derived address sets | Domain address allocator/reservations | App create reads | `domain::ipv4_allocation_skips_server_occupied_reserved_network_and_broadcast`; `domain::ipv6_allocates_first_free_address_from_a_very_large_range`; `tui_workflow::create_form_submits_automatic_dual_stack_client_without_changing_saved_state` | M21 |
### Peer and runtime projections
| Current projection | Business meaning / authority | Proposed owner and consumers | Existing proof | Migration |
| --- | --- | --- | --- | --- |
| `PeerInspection`, `PeerIssue`, `DocumentIssue`, `ConfigDocument` (`src/wireguard/document.rs:104-176`) | Every authoritative server peer section, byte spans, parsed routes and anomalies. Identity can be absent or duplicated. Malformed/no-key peers remain inspectable by document position. | `wireguard/document.rs`; app editing/deletion/restart and application read model consume it | `wireguard::wireguard_peer_issues_report_missing_malformed_and_duplicate_identity_fields`; `tui::malformed_peer_can_be_inspected_by_document_location_without_mutation_identity`; `tui::duplicate_public_keys_remain_inspectable_by_peer_position_but_not_mutable` | M22/M25/M26 |
| `ClientDisplay` / `SessionDisplay` (`src/app.rs:39-77,559-600`) | Secret-free current draft-document rows; `mutable_identity` only for one supported and unique projected client; metadata alias is descriptive, not independent authority | Move fact construction into app-owned `app/read_model.rs`; renderer consumes it | `tui::enter_opens_an_unsupported_peer_for_inspection_without_mutation_identity`; `tui::issues_screen_hides_valid_address_shape_limits_but_keeps_genuine_errors` | M26 |
| `SessionView::from_application`, `ClientStateRow`, `VisibleRecord` (`src/tui/state.rs:44-165`) | Current duplicate app projection plus UI generation, saved peer count, state map, reservation facts and visible removed rows | Split: application owns public secret-free view and action eligibility; TUI retains selection, screen, scroll and rendering state | `tui_workflow::saved_peer_projection_counts_legacy_peers_without_inventing_managed_state`; `tui::keyboard_navigation_reaches_draft_removed_rows_without_authorizing_them`; `tui::refresh_preserves_selection_by_public_key_not_alias_or_row_position` | M26 |
| Renderer-derived state and issue text (`src/tui/render.rs:1198-1265,1759-1835`) | Display labels/counts assembled from projection facts; currently some business categories are inferred in UI | Move counts/classification to read model only where authoritative facts permit; keep styling, wrapping, geometry and final strings/rendering TUI-owned | `tui::persistent_details_follow_the_highlight_and_keep_geometry_fixed`; `tui::client_rows_show_draft_state_and_escape_anomalous_unsupported_peers`; `tui::issues_screen_hides_valid_address_shape_limits_but_keeps_genuine_errors` | M26 |
| `SavedPeerProjection`, `RuntimeObservation`, `TechnicalMatch` (`src/runtime.rs:19-218`) | Complete technical route set independent of manageable `Client`; valid subnet-route peers can be restart-verified without UI mutability. Handshakes/counters are not apply evidence. | Runtime owns projection/comparison and read-only transport; app restart scenario coordinates; read model may expose only status/findings needed for UI | `runtime::projection_keeps_valid_unsupported_routes_and_ipv4_only_peers`; `runtime::counters_and_handshake_changes_do_not_change_technical_match`; `app::restart_verifies_metadata_free_legacy_routes_without_partial_applied_snapshot` | M20 name clarification; M25/M26 |
### Authorization, prepared contexts and worker generations
| Current context | Meaning and authority | Proposed owner | Readers / writers | Existing proof | Migration |
| --- | --- | --- | --- | --- | --- |
| UI selection guard `UiState::selected_client_mutable` (`src/tui/state.rs:473-491`) | Presentation-level guard requires one matching row, matching document position if selected, and a mutable identity; cannot grant operation authority | TUI selection state only; application owns typed per-action eligibility and execution revalidation | Terminal key handlers and footer hints read it; app methods/editors independently reject invalid actions | `tui::selection_uses_peer_position_for_duplicate_keys_and_disables_mutation`; `tui::removed_saved_client_is_visible_and_never_mutable` | M26 |
| Current rename/delete/delivery UI prerequisites (`src/tui/terminal.rs:771-823`) | Current TUI rename/delete guards both require a supported unique mutable identity; delivery/reissue additionally require saved membership. Separately, `Application::delete_client` supports deletion by public key for some exact-host-route peers outside `ClientStates`. M26 must audit this mismatch against the planned per-action eligibility contract; do not infer that current keyboard UI exposes that app-level capability or silently prohibit the documented supported path. | Application reports typed per-action eligibility, then each application operation revalidates its own preconditions; terminal maps eligibility to enabled actions | Terminal selects target; `ConfigDocument`/app authoritatively validate at execution | `tui::detail_hints_only_offer_saved_delivery_for_mutable_saved_selection`; `app::deletion_of_unprojectable_peer_persists_its_exact_host_route_reservation`; `app::deletion_of_unprojectable_peer_with_broad_route_fails_without_draft_change` | M26 |
| `InitialIssue`, `IssueContext`, `FirstIssueAuthorization`, `IssueStage` (`src/app.rs:96-126,976-1219`) | Session-local secret and one-use authorization bound to identity, peer directives and encrypted record; retries reflect publication/cleanup uncertainty | `app/delivery.rs` owns session lifecycle; vault owns ciphertext/crypto; file adapter owns publication mechanics | Create registers; Commit enables; each delivery validates/consumes; edits/delete/discard invalidate as appropriate | `app::committed_first_issue_publishes_selected_file_and_consumes_eligibility`; `app::published_file_is_reported_when_saved_store_bookkeeping_fails`; `app::failed_first_delivery_requires_consuming_capability_before_retry`; `app::temporary_cleanup_failure_requires_manual_cleanup_before_first_issue_retry` | M20/M24 |
| `Application::vault_draft_mut` (`src/app.rs:1310-1314`) | Public unrestricted mutable vault escape hatch | Remove or narrow after callers migrate; vault logic stays in vault/app draft owners | Only current caller: colocated app test `replacing_the_encrypted_record_invalidates_committed_first_issue` (~3525); no production, startup, integration or container-driver caller | Same test plus `app::deletion_and_discard_revoke_only_session_bound_initial_material` | M24/M27; replace test setup intentionally before visibility removal |
| `ApplicationExecutor::prepared_destination`, `restore_preview` (`src/tui/events.rs:290-301`) | Worker-owned continuation state bound to target, delivery mode/QR choice, attempt, preview and request generation; prepared-destination tuple currently has six elements and restore-preview tuple three | Private named contexts in `tui/events.rs` or a cohesive private executor child | `ApplicationExecutor::execute_at` and `execute_sensitive_at`; clears on unrelated actions, consumes on confirmation/cancel | `events::prepared_delivery_and_restore_confirmations_are_bound_to_the_reviewed_generation`; `client_delivery::overwrite_confirmation_is_bound_to_the_selected_destination`; TUI workflow restore cancellation/confirmation | M20 |
| `PendingOperation`, `SessionView.generation`, worker generation (`src/tui/events.rs:168-189,755-888`; `src/tui/state.rs:52-66,226-235`) | Monotonic identity for submitted work and returned view; stale view/statistics cannot overwrite current UI state | TUI worker/execution adapter only; not domain state | Mutating submit increments; worker tags result; UI accepts matching generations; periodic statistics rechecks generation across read | `events::delayed_statistics_are_bounded_and_do_not_advance_generation`; `events::statistics_started_before_a_mutation_are_discarded_as_stale`; `tui::stale_view_is_rejected_and_unsupported_selected_peer_is_not_mutable` | M20/M26 |
| `FormController`, `SecretInput`, `SecretText`, `SensitiveCommand` (`src/tui/forms.rs:50-145`; `src/tui/events.rs:78-100`) | Short-lived form state and zeroizing password command; secrets excluded from view/debug/clone | Forms own input; worker carries secret command; application consumes only during vault operation | Form submits sensitive command; worker invokes setup/reissue; form clears on cancel/consumption | `tui::compact_form_hints_follow_the_active_form_without_leaking_input`; `vault::every_retrieval_requires_the_correct_password`; `client_delivery::reissue_unlocks_installed_saved_record_each_time_and_warns_after_reopen` | M24/M26; preserve lifetimes |
### Public API escape-hatch callers and removal conditions
| API | Actual callers | Safe path and timing |
| --- | --- | --- |
| `ClientStates::draft_mut` (`src/domain.rs:233`) | Production: `Application` methods only (`app.rs:611,680,1259,1301`). Other direct callers: `domain.rs` unit tests. No `lib.rs`, startup, TUI, integration-test or container-driver consumer. | Replace application uses with narrow stage/rename/remove operations as M21/M22 move transitions. Update colocated tests to exercise those operations. Reduce visibility only after the complete source and test search finds no external caller. |
| `Application::vault_draft_mut` (`src/app.rs:1312`) | Only app unit test `replacing_the_encrypted_record_invalidates_committed_first_issue`; no production or integration caller. | Replace test mutation with a purpose-built test fixture or behavior through a legitimate application operation; do not retain a production mutation bypass for tests. Remove/narrow in M24 or M27 after verification. |
| `Application::add_client` (`src/app.rs:605`) | No production caller. Integration: `tests/commit_restore.rs` at archive-cycle, reservation and backup-failure setup; `tests/runtime_restart.rs` fixture helper; app unit restart setup. Distinct `ConfigDocument::add_client` remains used by application/editor and preservation tests. | Preserve app facade operations through M22, migrate fixture uses to deliberate setup or create workflow while keeping assertions, then remove only if full caller audit confirms no external library consumer. M27 owns final pruning. |
| `Application::open(store, states)` (`src/app.rs:493`) | App unit tests and integration fixture constructors in `client_delivery.rs`, `commit_restore.rs`, `runtime_restart.rs`, `tui_workflow.rs`. No production startup caller. | Keep compatibility while tests depend on seeded state. Migrate tests that need no injected state to `open_from_saved_store`; tests that specifically validate state seeding may retain a justified constructor. Remove only after binary, library, integration and test-only container callers are checked in M27. |
| `Application::open_from_saved_store` (`src/app.rs:501`) | Production: `startup::open_management_session` (`src/startup.rs:65-92`). Also application/event/TUI tests and `container_driver::open_fixture_application`. `container_driver` itself is library `#[cfg(test)]` only (`src/lib.rs:14-17`). | Required production entry point; retain or provide an equivalent public facade. Fixture driver has no production fixture-apply capability. |
## Transition tables
These tables preserve observed behavior. “No mutation” below means no protected
saved-file or runtime mutation; drafts may remain in memory where specified.
### Draft lifecycle
| Trigger | Preconditions | Next state | Side effects | Failure or cancellation | Existing proof |
| --- | --- | --- | --- | --- | --- |
| Open session | Saved settings/config validate; optional state/vault parse | Draft begins from saved document and client snapshot; current apply knowledge is Unknown | Read installed bytes, restore conservative reservations, load history separately | Invalid saved state rejects open; history never promotes current apply knowledge | `startup::management_session_loads_supported_state_and_keeps_unsupported_peers_visible`; `app::verified_history_persists_across_reopen_without_marking_runtime_applied` |
| Create client | Vault exists; alias/address inputs valid; configured ranges available | Document/client/vault drafts all contain new peer/record; first-issue material registers only after staging succeeds | Generate key, allocate/check dual stack, encrypt record, write no files | Any preparation failure leaves all live draft components and issue rights unchanged | `app::new_client_preparation_is_transactional_and_uncommitted_keys_cannot_issue`; `app::manual_addresses_reject_occupied_and_out_of_range_without_partial_staging` |
| Rename or delete | Target identity resolves to an authoritative unambiguous peer; deletion of unprojectable peer can reserve only exact host routes | Corresponding document and manageable snapshot edit; delete removes its vault record if present | Mutate in-memory drafts; retain exact unsupported routes pending Commit; invalidate relevant issue eligibility | Malformed/ambiguous/unsafe broad-route edit fails without partial draft change | `app::deletion_of_unprojectable_peer_with_broad_route_fails_without_draft_change`; `app::deletion_only_removes_selected_current_record_and_retains_other_records` |
| Discard draft | Session can reread saved bytes | Draft returns to installed saved state | Reparse installed document/vault, restore persisted reservations; retain only still-valid awaiting-first-file issue material | Read/parse failure returns error; no installed writes; already-consumed delivery is not recreated | `app::discard_restores_draft_without_writing_installed_bytes`; `app::deletion_and_discard_revoke_only_session_bound_initial_material` |
| Commit | Draft validates; mandatory pre-Commit archive succeeds | Saved state advances to installed draft only after successful recoverable install | Persist document, encrypted vault and internal reservations; no runtime call; apply knowledge becomes unknown when saved differs | Backup failure stops before protected writes; partial install reports failure/recovery; draft remains retryable | `app::backup_failure_preserves_installed_files_and_draft`; `app::injected_archive_and_install_failures_preserve_bytes_and_session_saved_state`; `app::commit_advances_saved_state_without_applying_and_backup_contains_old_bytes` |
| Restart with draft | Valid saved-state restart preview; explicit choice | Cancel keeps draft; discard choice resets it before restart sequence | Separate restart flow; Commit never applies | Cancel makes no backup/job/runtime call. Once service request is submitted, cancellation/timeout cannot assert that the job did not finish | `app::restart_cancel_preserves_draft_and_never_calls_adapters`; `app::service_timeout_reports_submission_and_may_still_complete` |
### Address reservations
| Trigger | Preconditions | Next state | Side effects | Failure or cancellation | Existing proof |
| --- | --- | --- | --- | --- | --- |
| Delete saved manageable client in draft | Target's addresses known | No committed reservation yet; addresses remain occupied by saved snapshot | Draft deletion only | Discard restores client and no pending reservation is published | `domain::a_draft_only_deletion_does_not_create_a_committed_reservation` |
| Delete saved unprojectable peer in draft | Every route to be retained is an exact IPv4 `/32` or IPv6 `/128` host route | Exact routes enter draft-only reservation set | Prevent allocation while deletion is staged | Broad/subnet route or missing address evidence rejects deletion without draft mutation | `app::deletion_of_unprojectable_peer_persists_its_exact_host_route_reservation`; `app::deletion_of_unprojectable_peer_with_broad_route_fails_without_draft_change` |
| Commit deletion | Mandatory backup and saved install succeed | Deleted saved addresses enter persisted reservation set; committed client snapshot advances | Allocator excludes current draft/saved and reserved addresses | Backup/install failure does not publish release or new saved state | `domain::discard_restores_draft_and_commit_keeps_applied_snapshot_separate`; `app::state_file_round_trip_keeps_pending_addresses` |
| Restore archive removing current peers | Preview is valid and confirmation accepted; pre-restore backup succeeds | Rebuilt internal state carries existing persisted reservations and addresses from `ClientStates::saved()`, except addresses assigned by restored manageable clients. `preview_restore` does not collect exact host routes from unprojectable peers in the installed saved server configuration or `draft_unsupported_reservations`; their preservation is not established. | Full saved replacement only; runtime is untouched | Cancel leaves saved state unchanged; backup/install failure leaves recoverable prior state and drafts | `commit_restore::restore_reserves_addresses_of_current_saved_clients_that_the_archive_removes`; `app::partial_restore_failure_preserves_session_and_pre_restore_backup_then_reopens_to_old_set` |
| Verified restart publication | Confirmed attempt still matches installed generation; completed service job; complete interface/peer match; internal-state install succeeds | Reservations for removed clients clear | Persist verified timestamp/state, then publish current applied knowledge | Mismatch/read/service/timeout/persistence failure retains reservations and conservative apply knowledge | `domain::successful_verified_apply_releases_deleted_addresses_and_mismatch_does_not`; `app::restart_mismatch_and_post_restart_write_failure_never_release_addresses` |
### Current and historical verification
| Trigger | Preconditions | Next state | Side effects | Failure or cancellation | Existing proof |
| --- | --- | --- | --- | --- | --- |
| Open/reopen | Saved configuration and internal state parse | Current state Unknown; optional management applied snapshot absent; historical timestamp remains separately available | Restore reservations from persisted metadata only | Invalid internal state rejects session; historical success is not live proof | `persistence::persisted_history_never_marks_the_runtime_verified`; `app::verified_history_persists_across_reopen_without_marking_runtime_applied` |
| Periodic statistics read | Worker has an interface name; bounded read returns observation | UI statistics update only | No domain apply transition, no saved write, no reservation release | Unavailable/unknown data remains so; stale-generation result is discarded | `tui::events::statistics_started_before_a_mutation_are_discarded_as_stale`; `tui::state::statistics_refresh_is_generation_bound_and_never_changes_applied_state` |
| Saved state changes | Commit/restore changes installed generation | Current apply knowledge becomes Unknown; old historical timestamp may remain or be cleared according to existing successful restore bookkeeping | No runtime operation | Failed write leaves in-memory published state conservative | `app::commit_advances_saved_state_without_applying_and_backup_contains_old_bytes`; `app::successful_restore_clears_verified_current_history_but_backup_retains_it` |
| Restart comparison matches complete saved technical peer set | Interface identity and complete peer dump match; malformed/duplicate/unverifiable records absent | Still not published until durable verification bookkeeping succeeds | Prepare persisted reservations/timestamp update | Persistence failure leaves in-memory apply knowledge/reservations unchanged | `app::confirmed_restart_orders_backup_job_read_and_durable_publication`; `app::failed_verified_history_write_does_not_advance_memory_or_reopen_as_verified` |
| Full technical match with peer outside management projection | Peer record is valid/verifiable even if not a `Client`, e.g. subnet routes | Knowledge may be Verified with no complete `applied_snapshot` | Persist verified result; do not invent partial management snapshot | Any incomplete runtime dump still cannot pass | `app::restart_verifies_metadata_free_legacy_routes_without_partial_applied_snapshot`; `runtime::projection_keeps_valid_unsupported_routes_and_ipv4_only_peers` |
### First delivery and password reissue
| Trigger / stage | Preconditions | Next state | Side effects | Failure or cancellation | Existing proof |
| --- | --- | --- | --- | --- | --- |
| Create before Commit | Successful coordinated create in this session | Pending draft issue | Hold generated private key in session and bind peer directives/ciphertext | Any failed create publishes no new issue authorization | `app::new_client_preparation_is_transactional_and_uncommitted_keys_cannot_issue` |
| Commit succeeds | Created peer and encrypted record appear in installed state and match session context | Awaiting first file | Promote one-use capability for current session | Commit failure keeps retryable draft capability but does not permit output | `app::commit_failure_keeps_pending_issue_and_successful_retry_authorizes_it` |
| Prepare destination | Eligible committed issue or saved reissue selected; output path validated | Prepared destination is bound to identity, mode, QR request, attempt and generation | No key disclosure or file write yet | Unrelated command/cancel invalidates the prepared context; stale generation requires reselection | `events::prepared_delivery_and_restore_confirmations_are_bound_to_the_reviewed_generation`; `client_delivery::overwrite_confirmation_is_bound_to_the_selected_destination` |
| Confirm first file | Same session, context still matches saved record, overwrite confirmation if required | File is published; first issue is consumed only after saved bookkeeping | Secure file publication; optional QR derives from the same payload and happens after mandatory file | Before-publication failure can retry; uncertain publication requires overwrite confirmation; cleanup failure requires manual cleanup; QR failure does not undo file | `app::failed_file_publication_can_retry_in_a_different_directory`; `app::uncertain_first_file_publication_retains_warning_and_skips_qr`; `app::temporary_cleanup_failure_requires_manual_cleanup_before_first_issue_retry`; `app::file_and_qr_share_payload_and_qr_failure_still_consumes_first_issue` |
| Saved bookkeeping after file | File publication succeeded | Consumed if bookkeeping succeeds; otherwise explicit published-but-uncertain result, no automatic retry capability | Report file exists; preserve activation warning | Do not misreport as failed/unpublished or auto-delete file | `app::published_file_is_reported_when_saved_store_bookkeeping_fails` |
| Reissue after session/reopen | Selected client is in saved state; password supplied for this action | No first-issue session eligibility is involved | Unlock matching saved vault record, render/write selected client file; optional QR | Wrong password/record mismatch/file failure is reported without secret leakage; each client requires fresh password | `client_delivery::reissue_unlocks_installed_saved_record_each_time_and_warns_after_reopen`; `vault::every_retrieval_requires_the_correct_password`; `client_delivery::imported_ipv4_only_parameters_are_preserved_and_authenticated_conflict_blocks_reissue` |
### Restore preview and confirmation
| Trigger | Preconditions | Next state | Side effects | Failure or cancellation | Existing proof |
| --- | --- | --- | --- | --- | --- |
| Select archive for preview | Safe archive name and valid archive format | Opaque preview holds validated bytes plus metadata; UI receives redacted summary/change list and generation | Read-only archive validation; no password and no runtime call | Invalid/traversal archive rejected; cancel drops preview | `app::preview_is_read_only_and_confirm_restores_without_unlocking_vault`; `commit_restore::archive_parser_rejects_traversal_links_duplicates_and_oversized_members` |
| Confirm matching preview | Preview token still belongs to the operation generation; installation paths remain compatible | Selected archive replaces saved settings, server configuration and vault as a whole after mandatory pre-restore backup; running VPN remains unchanged | Preview rebuilds internal state instead of preserving its archive bytes: retain archived reservations and current reservations, add addresses of currently saved clients removed by the archive, then exclude addresses assigned by restored clients. The rebuilt state has no historical verification timestamp; current apply knowledge becomes Unknown. No restart occurs. The pre-restore backup retains the prior saved bytes and history. | Stale/cancelled preview cannot write; backup/install failure preserves prior saved state or requires journal recovery | `events::prepared_delivery_and_restore_confirmations_are_bound_to_the_reviewed_generation`; `app::restore_accepts_changed_non_path_settings_and_rejects_changed_paths`; `commit_restore::restore_reserves_addresses_of_current_saved_clients_that_the_archive_removes`; `app::successful_restore_clears_verified_current_history_but_backup_retains_it`; `commit_restore::backup_failure_blocks_commit_and_restore_without_losing_drafts_or_saved_bytes` |
| Cancel preview/confirmation | No restore install has begun | Existing saved state and draft remain | Clear app-executor preview context | No backup/install/runtime side effect | `tui_workflow::worker_backup_restore_preview_cancel_and_confirm_are_separate_saved_operations`; app preview read-only test above |
### Restart attempt identity and uncertainty
| Trigger | Preconditions | Next state | Side effects | Failure or cancellation | Existing proof |
| --- | --- | --- | --- | --- | --- |
| Preview restart | Installed bytes equal session saved generation; interface/service settings and complete saved peer projection are safe | Read-only preview captures saved bytes/settings/projection and draft presence | No backup/job/runtime operation | Unsafe or ambiguous peer state blocks before backup; stale saved bytes reject confirmation | `app::unsafe_saved_peer_diagnostics_block_preview_before_backup_with_safe_location`; `app::restart_confirmation_rejects_a_generation_changed_after_preview` |
| Cancel choice | Preview still valid | Cancelled report; draft remains | No backup, service request, runtime read, or persistence | No operation was submitted | `app::restart_cancel_preserves_draft_and_never_calls_adapters` |
| Confirm restart | Preview still current; explicit choice; backup succeeds | Attempt identity bound to saved generation, then service job submitted | Optional draft discard at specified safe point; complete backup; one shared deadline | Backup failure submits no job; before submission cancellation is definite | `app::restart_backup_failure_preserves_draft_and_submits_no_job`; `app::confirmed_restart_orders_backup_job_read_and_durable_publication` |
| Service job submitted | Request accepted | Await job completion under same deadline | Job may still complete even if caller times out/disconnects | Timeout report retains submitted/may-still-complete distinction; never claim cancellation or rollback | `app::service_timeout_reports_submission_and_may_still_complete`; `app::submitted_terminal_job_failures_cannot_still_complete` |
| Job complete, observe and compare | Job successful; remaining shared deadline; expected configured interface and complete runtime peer set | Matching result becomes candidate for durable verification | Bounded observation and pure comparison; no direct state publish yet | Read error/timeout/missing interface/incomplete dump/mismatch stops publication and retains reservations | `app::shared_deadline_expired_after_successful_job_stops_before_runtime_read`; `app::shared_deadline_expired_after_runtime_read_reports_timeout_before_comparison`; `runtime::partial_duplicate_and_unavailable_records_never_pass` |
| Durable verification succeeds | Attempt token and both current/attempt saved bytes still equal; full technical match; internal state install succeeds | Publish current verified knowledge and clear retained addresses | Persist timestamp/reservations first; update in-memory session after successful install | Failed persistence leaves conservative state; no second restart or automatic rollback | `app::failed_verified_history_write_does_not_advance_memory_or_reopen_as_verified`; `app::restart_mismatch_and_post_restart_write_failure_never_release_addresses` |
## Destination modules and staged migration
These destinations are bounded homes for current responsibilities, not a request
to create empty files. Keep established public facade calls until callers have
migrated. Existing low-level adapters and persisted formats remain in place.
| Milestone | Production surface and destination | Compatibility / preserved boundary | Existing baseline and exact focused commands |
| --- | --- | --- | --- |
| M20 — vocabulary and named contexts | `domain.rs` apply-knowledge naming; `runtime.rs` measured observations; `tui/state.rs` presentation mapping; `tui/events.rs` private named prepared-delivery and restore contexts. Split coherent domain children only if useful. | No business-rule movement, new type semantics, schema change, secret Clone/Debug, or command redesign. Replace the six-element prepared-destination tuple with named fields for identity, QR choice, delivery mode, retry attempt, generation and destination. Keep public imports via re-export only when actual callers need them. | `cargo test --locked domain::`; `cargo test --locked runtime::`; `cargo test --locked tui::events::`; `cargo test --locked tui::state::`. Proof names: `verified_state_requires_a_matching_applied_snapshot`, `counters_and_handshake_changes_do_not_change_technical_match`, `prepared_delivery_and_restore_confirmations_are_bound_to_the_reviewed_generation`. |
| M21 — reservations and apply knowledge | `domain/reservations.rs` and `domain/apply.rs` as justified; allocator remains domain; `persistence.rs` retains TOML serialization; `runtime.rs` retains technical matching; app restart caller migrates to transitions. | Preserve PersistedState version/field names and semantics, exact host-route retention, Unknown on reopen, optional incomplete management snapshot, and no public unrestricted mark-applied API. | `cargo test --locked domain::`; `cargo test --locked persistence::`; `cargo test --locked app::`. Proof names: reservation and verified-release domain tests; `persisted_history_never_marks_the_runtime_verified`; `restart_mismatch_and_post_restart_write_failure_never_release_addresses`. |
| M22 — coordinated draft and create/edit/import | `app/draft.rs` coordinates document, client projection, encrypted vault and draft-only reservations; cohesive `app/clients.rs` and `app/import.rs` only if responsibility warrants. Keep `Application` facade methods. Domain mutators become narrow; `vault/source.rs` still reads sources and `vault/import.rs` reconciles/encrypts. | Prepare fallible replacements before publication; document remains authoritative; no file/runtime writes; no partially staged issue capability. Preserve original files, anomaly reports, metadata and unsupported peers. Retain app facade entry points while startup/tests migrate. | `cargo test --locked app::`; `cargo test --locked --test config_preservation`; `cargo test --locked --test client_delivery`; `cargo test --locked --test vault_import`; `cargo test --locked --test tui_workflow`. Existing coverage: app create/import/deletion tests and `initial_setup_encrypts_valid_files_and_leaves_sources_and_server_unchanged`. |
| M23 — saved Commit, backups and restore | `app/commit.rs`, `app/restore.rs`, `app/backups.rs` own ordered scenarios; `persistence.rs` keeps journaling/path safety and `backup.rs` keeps archive encoding/parsing. | No persisted format, archive member/version, settings-path policy, rollback, runtime or service behavior changes. Keep preview byte-bound and saved-only. | `cargo test --locked app::`; `cargo test --locked persistence::`; `cargo test --locked backup::`; `cargo test --locked --test commit_restore`; `cargo test --locked --test client_delivery`. Proof includes commit/install failure and restore preview/cancel/backup tests. |
| M24 — delivery scenarios | `app/delivery.rs` owns first-issue session eligibility, retry and password reissue orchestration; `delivery.rs`, `delivery/files.rs`, `delivery/qr.rs`, vault crypto stay focused adapters. | Preserve one-use/session-only secret capability, prepared destination ownership, same payload for file/QR, per-client password and publication uncertainty/manual cleanup. | `cargo test --locked app::`; `cargo test --locked delivery::`; `cargo test --locked vault::`; `cargo test --locked --test client_delivery`; `cargo test --locked --test tui_workflow`. |
| M25 — restart scenario | `app/restart.rs` owns preview, attempt token, backup/job/read/compare/durable-publication order. `runtime.rs` owns complete peer projection/comparison; `service.rs` owns systemd adapter; persistence owns durable state. Keep test-only fixture apply behind `#[cfg(test)]`. | Preserve shared deadline, generation/attempt checks, job-submission uncertainty, unsupported-but-verifiable peers, and reservations until durable success. No real host service calls during tests. | `cargo test --locked app::`; `cargo test --locked runtime::`; `cargo test --locked service::`; `cargo test --locked --test commit_restore`; `cargo test --locked --test runtime_restart`. |
| M26 — read model and thin TUI command adapter | `app/read_model.rs` owns secret-free peer/state/issue/reservation/apply facts and per-action eligibility. `tui/state.rs` retains UI navigation/generation; events/executor owns worker lifecycle and command adaptation; forms own transient inputs; renderers own visual strings/styles/layout. | No TUI parsing of saved bytes or duplicated authorization policy. Preserve malformed-row inspection by document position, draft removals, action-specific eligibility, QR disclosure, keyboard behavior, worker serialization/generations/cancellation/shutdown and layout. | `cargo test --locked tui::`; `cargo test --locked --test tui_workflow`; `cargo test --locked --test client_delivery`. Exact worker, view-generation, selection and PTY tests are listed above. |
| M27 — integrated cleanup and handoff | Reconcile actual final owners in source, `docs/PROJECT_LAYOUT.md`, glossary and a reading guide; remove obsolete forwarding APIs only after all callers have migrated. | No feature addition, format/dependency changes, assertion weakening, or real-system claim. Review library/binary/integration/test-only container callers and M8 evidence invalidation. | `cargo test --locked --test config_preservation`; `cargo test --locked --test commit_restore`; `cargo test --locked --test client_delivery`; `cargo test --locked --test runtime_restart`; `cargo test --locked --test vault_import`; `cargo test --locked --test tui_workflow`; plus required RUST gates. |
### Test inventory gaps and verification notes
- There is no `tests/client_lifecycle.rs` target. Lifecycle coverage is
distributed across `app::`, `client_delivery`, `commit_restore`,
`vault_import`, and `tui_workflow`; the planned M22–M26 commands use existing
targets, and a new target is needed only if acceptance finds a real behavioral
gap.
- There is no dedicated current integration test named for application
projection/action eligibility. Existing checks are spread across `app::`
display logic, `tui::state`, `tui::render`, and `tui_workflow::saved_peer_projection_counts_legacy_peers_without_inventing_managed_state`.
- Restore reservation coverage currently proves only that addresses from
manageable clients in `ClientStates::saved()` survive replacement
(`commit_restore::restore_reserves_addresses_of_current_saved_clients_that_the_archive_removes`).
`Application::preview_restore` unions persisted reservations with those
manageable saved-client addresses; it does not read exact host routes from
unprojectable peers in the installed saved server configuration or include
`draft_unsupported_reservations`. The existing policy keeps deleted-client
addresses reserved until verified restart, so this is a potential correctness
gap, not an agreed exception. M23 should add focused restore coverage for
these sources and repair any uncovered preservation under the existing
requirement before claiming they are reserved.
- `ClientStates::draft_mut` and `Application::vault_draft_mut` have no external
production consumers. The latter currently exists in the public API solely
to support one colocated app test. App `add_client` and `open` have public
integration-test fixture consumers, so their removals need an explicit
fixture/API migration, not just a `pub` reduction.
- Existing test-only container driver is `src/container_driver.rs` included only
by `src/lib.rs` under `#[cfg(test)]`. Its normal fixture tests are synthetic;
ignored `container_driver::alpine_scenario_driver` is opt-in and is not an
ordinary host test or a systemd acceptance test. Do not run it as part of
this documentation task.
- M19.2 and the subsequent migrations use only approved dependencies already
present. No dependency, manifest, lockfile, configuration, or product behavior
change is implied.
+132
View File
@@ -0,0 +1,132 @@
# Milestone 19 evidence: refactoring contract
Recorded 2026-10-29T22:23:28+02:00. Base commit:
`23d6180f2478f636c185d67b8bb3788d21656ce3`. The working tree is dirty:
`PLANS.md` already held the user-approved M19–M27 plan before execution, and
this milestone changed `PLANS.md` and `CONTEXT.md` and added
`docs/architecture/domain-refactoring.md` and this report. No Rust source,
manifest, lockfile, production configuration, or generated secret was changed.
The reviewed substantive document inputs have these SHA256 fingerprints:
| Input | SHA256 |
| --- | --- |
| `PLANS.md` (accepted snapshot) | `724bb710c2f8db7ceca7a059425be6a868e23ab9dd41cb1ff5ae5c32986a4b8e` |
| `CONTEXT.md` | `06efa3f8bb91dbd3b7fb6a00f0035dde7c3be1c01d0a6fe9c096460d6d28deb8` |
| `docs/architecture/domain-refactoring.md` | `7dbb01cfa85dd89a3d122ecf8a564dd204c805bea8a33cb09c4268d0308329c8` |
## Scope and acceptance mapping
M19.1 read-only inventory traced the domain/application, WireGuard document,
runtime, persistence/backup, vault/import/source, delivery, startup, TUI,
library, test-only container driver, and integration tests. M19.2 added a
domain-only glossary and a source-referenced migration contract. The contract
maps field/function authority, readers/writers, actual test names and M20–M27
owners, and describes draft, reservation, verification, delivery, restore and
restart transitions. It keeps the server document authoritative for malformed,
metadata-free and unsupported peers, including valid subnet-route peers that
can be verified technically without becoming manageable clients. Historical
verification is distinct from current apply knowledge. This supports M19-A1–A3.
The caller audit found `ClientStates::draft_mut` used by app production code and
colocated tests; `Application::vault_draft_mut` by one colocated test;
`Application::add_client` and `Application::open` by integration/test fixtures;
and `open_from_saved_store` by production startup and the test-only container
driver. The contract retains compatibility until actual callers migrate.
M20 tuple arity is six; M22–M26 planned focused commands name present targets.
No dependency, schema, archive, UI or behavior change is implied.
The remaining behavior gap is explicit in the contract: current restore
reservation coverage proves manageable saved clients, while
`preview_restore` does not collect exact host routes from unprojectable current
peers or `draft_unsupported_reservations`. M23 must add focused coverage and
repair any uncovered preservation under the existing reserved-address policy.
M19 does not claim that this behavior is already correct. M9 remains retired
and unaccepted; M19 depends on accepted M18, not M9.
## Independent review and repair
A GPT-6-Luna/high implementer completed M19.1 and M19.2. A separate, fresh
GPT-6-Luna/high reviewer inspected the actual source, callers, tests and
documents. A GPT-6-Luna/high repairer corrected architecture wording; the
coordinator alone corrected plan wording and later task commands. The reviewer
rechecked each repair and reported no remaining actionable findings:
| Finding | Final disposition |
| --- | --- |
| M19-R1 | Closed: restore rewrites internal state, retains conservative reservations and clears historical verification. |
| M19-R2 | Closed: historical M18 handoff no longer contradicts active M19. |
| M19-R3 | Closed for this documentation milestone: current unprojectable-peer restore reservation gap and M23 focused test/repair are recorded. |
| M19-R4 | Closed: programme and historical M17 handoff wording reflect the current plan. |
| M19-R5 | Closed: M19.3 task row reflects the current finding disposition. |
| M19-R6 | Closed: tuple arity and later focused test targets match the actual repository. |
This independent review supports M19-A4 subject to the final DOC checks below.
## Validation and limits
Final integrated DOC checks ran in the repository root on the accepted plan
snapshot. The exact link/whitespace command was:
```sh
python3 - <<'PY'
from pathlib import Path
import re, sys
files = [Path(p) for p in ('PLANS.md', 'CONTEXT.md', 'docs/architecture/domain-refactoring.md', 'docs/evidence/milestone-19.md')]
errors = []
links = anchors = 0
for file in files:
body = file.read_text()
if not body.endswith('\n'): errors.append(f'{file}: final newline')
for number, line in enumerate(body.splitlines(), 1):
if line.rstrip() != line: errors.append(f'{file}:{number}: trailing whitespace')
for target in re.findall(r'(?<!!)\[[^]]+\]\(([^)]+)\)', line):
if '://' in target or target.startswith(('mailto:', 'plugin:')): continue
path, _, anchor = target.partition('#')
dest = (file.parent / path).resolve() if path else file.resolve()
links += 1
if not dest.exists(): errors.append(f'{file}:{number}: missing {target}'); continue
if anchor:
anchors += 1
headings = [re.sub(r'[^\w\- ]', '', h.lower()).strip().replace(' ', '-') for h in re.findall(r'^#{1,6}\s+(.+)$', dest.read_text(), re.M)]
if anchor not in headings: errors.append(f'{file}:{number}: missing anchor {target}')
print(f'{len(files)} files, {links} local links, {anchors} anchors, {len(errors)} errors')
for error in errors: print(error)
sys.exit(bool(errors))
PY
```
It exited 0: four files, 72 local links, zero anchors and zero errors. The
new untracked documents were included in the final-newline and trailing-space
check. The exact plan-consistency command was:
```sh
python3 - <<'PY'
from pathlib import Path
import re, sys
plan = Path('PLANS.md').read_text()
rows = [(int(i), state) for i, state in re.findall(r'^\|\s*(\d+)\s*\|[^\n]*?\|\s*(accepted|retired|pending|in_progress|in_review|needs_changes|blocked)\s*\|$', plan, re.M)]
errors = []
if [i for i, _ in rows] != list(range(1, 28)): errors.append('register numbering')
states = dict(rows)
if states.get(18) != 'accepted' or states.get(19) != 'accepted' or any(states.get(i) != 'pending' for i in range(20, 28)): errors.append('register states')
record = plan.split('### Milestone 19 —', 1)[1].split('### Milestone 20 —', 1)[0]
if 'Status: accepted' not in record or any(f'M19.{i} |' not in record for i in (1, 2, 3)): errors.append('M19 record/tasks')
resume = plan.split('## Current progress and resume point', 1)[1].split('## Plan maintenance', 1)[0]
if 'M19 is accepted' not in resume or 'Stop after M19' not in resume: errors.append('resume point')
for target in set(re.findall(r'cargo test --locked --test ([a-z_]+)', plan.split('### Milestone 20 —', 1)[1].split('## Current progress and resume point', 1)[0])):
if not Path(f'tests/{target}.rs').exists(): errors.append(f'missing test target {target}')
if "prepared_destination's six-element tuple" not in plan: errors.append('tuple arity')
print(f'{len(rows)} register IDs, M19 tasks/status/resume, future test targets and tuple: {len(errors)} errors')
for error in errors: print(error)
sys.exit(bool(errors))
PY
```
It exited 0: 27 ordered register IDs and no status, task, target or tuple
errors. `git diff --check` exited 0. The coordinator also inspected factual
claims and the final changed-file set; the independent reviewer compared
the migration contract with production and test source. Rust formatting,
Clippy, tests, release build, Alpine Testcontainers and disposable-VM checks
were not required for documentation-only M19 and were not run. No production
or live WireGuard access occurred.