Files
wireguard-admin/PLANS.md
T

103 KiB
Raw Blame History

Execution Plan

Purpose and authority

This is the living execution plan for WireGuard Admin version 1. It records milestone scope, progress, review findings, and evidence so a new agent can resume from repository files without relying on chat history.

Read AGENTS.md, the specification, architectural decisions, implementation order, and the glossary before execution. The specification defines behavior; this plan schedules its delivery. Approved dependencies are in DEPENDENCIES.md; Alpine/VM testing rules are in TESTING.md. Do not change requirements to make a milestone pass.

Creating this plan does not start implementation or change the active model. All milestones below are initially pending, and no validation results are claimed.

Model roles

Role Required model Reasoning effort Responsibility
Coordinator GPT-6-Sol (gpt-6-sol) medium Refine tasks, dispatch workers, resolve interfaces, verify results, maintain this plan, accept milestones
Implementation worker GPT-6-Luna (gpt-6-luna) high Implement a bounded task and its meaningful tests
Independent reviewer GPT-6-Luna (gpt-6-luna) high Review the actual changes and evidence against acceptance criteria
Repair worker GPT-6-Luna (gpt-6-luna) high Fix review findings and regressions, then report new evidence

These are the user's explicit model choices, not suggestions. Configure them through the execution harness, not by merely telling an agent to impersonate a model. In a harness using spawn_agent, use model="gpt-6-luna", reasoning_effort="high", and fork_turns="none" for workers; supply their context explicitly. Do not silently substitute another model. If the required model or effort is unavailable, report the limitation before starting affected execution. This Markdown file cannot switch the calling coordinator's model.

Sequential execution and ownership

  • Number milestones with integers starting at 1. Keep IDs stable, never renumber accepted work or reuse IDs. Append new milestones using the next available integer; add bounded tasks within a milestone when finer scope is needed.
  • Execute milestones in ascending order. Start the next milestone only after the current milestone is accepted. Do not bypass an unmet gate because a later task looks independent.
  • The coordinator owns PLANS.md, task boundaries, interface agreements, and acceptance. Workers report proposed plan updates; they do not independently mark milestones accepted.
  • Default to one implementation writer at a time. Independent tasks within the active milestone may run concurrently only with explicit disjoint file ownership and agreed interfaces. Do not let agents overwrite one another's edits or run competing Cargo manifest/lockfile changes.
  • Use subagents for current-task work, not new user-owned chats. Reviewers should be separate agents from implementers and initially read-only. Further delegation must remain within the coordinator's assigned scope and model policy.
  • Preserve unrelated user changes. Do not create commits, publish, deploy, or access production merely because a milestone is accepted.

Milestone lifecycle and review loop

Use states pending, in_progress, in_review, needs_changes, blocked, and accepted. A blocked milestone records the concrete missing prerequisite; it is not accepted and its successors remain pending.

  1. Prepare. Inspect the actual repository. Expand the active milestone using the template below: concrete files/interfaces, small steps, observable outcomes, exact available commands, and expected results. Identify missing prerequisites before dispatch.
  2. Implement. Give a worker a bounded task brief. It implements only the assigned scope, adds focused behavioral tests, and reports changed files, commands/results, assumptions, and unfinished work.
  3. Verify and review. The coordinator examines the actual files/diff and checks evidence. A fresh reviewer evaluates correctness, regressions, specification/ADR compliance, security-relevant failure paths, and test coverage. Agent confidence or a prose summary is not acceptance evidence.
  4. Repair. Record each actionable finding with an ID, location, impact, expected behavior, and reproduction or validation. Assign a worker to fix it and rerun affected tests. The reviewer rechecks fixes and newly affected behavior.
  5. Repeat. Continue review and repair until findings are resolved and acceptance checks pass. Do not stop at an arbitrary iteration count or repeat an unchanged failing approach. Investigate repeated failures, reduce the task, or record a real external blocker. Settled requirements remain unchanged.
  6. Accept. The coordinator runs milestone gates against the integrated files, records reviewer disposition and validation evidence, updates progress/outcomes, and marks the milestone accepted. Only then advance. Routine acceptance does not require another user approval; new dependencies outside the approved set and unresolved product decisions retain their existing approval rules.

If a revision invalidates earlier acceptance evidence, record which milestones are affected and reopen the earliest affected milestone before advancing. Keep the integrated state buildable at each accepted milestone.

Worker task brief

Every dispatch must include:

  • Milestone ID, task ID, required model/effort, and role.
  • User-visible objective, relevant specification sections/ADRs, and prerequisite state.
  • Explicit read list, owned files, interfaces to preserve, and out-of-scope work.
  • Approved dependencies needed by this task; no permission to add unapproved dependencies.
  • Acceptance criteria and commands with expected outcomes; identify tests requiring Docker/VM access.
  • Safety boundary: synthetic fixtures only, no production keys/services/configuration, no host-network shortcuts.
  • Required report: changed files, checks actually run and their results, findings/risks, and remaining work. No secrets in reports.

For reviewers, provide the same objective and criteria plus the change baseline or exact changed-file list. Ask for independent inspection rather than confirmation of the implementer's conclusions.

Milestone record template

Use this structure when expanding each active milestone. Replace placeholders before dispatch; do not interpret them as completed work.

## Milestone <next integer> — <observable outcome>

Status: pending
Prerequisites: <accepted milestone IDs and environment requirements>

### Outcome and scope
<What becomes possible, boundaries, and links to governing requirements.>

### Context and implementation steps
<Actual paths, current state, interfaces, task ownership, and ordered steps.>

### Acceptance and validation
- [ ] <Observable behavior and test/demonstration proving it.>
- [ ] <Relevant failure behavior and how it is verified.>
Commands: <exact commands, working directory, prerequisites, expected results>

### Progress
- [ ] <Task ID and owner/agent ID; timestamp when updated>

### Review and repair
| Finding | Iteration | Location and impact | Owner | Resolution and evidence | State |
| --- | --- | --- | --- | --- | --- |

### Evidence and handoff
<Commands actually run, exit/results, artifact paths, reviewer disposition,
remaining limitations, next concrete action.>

### Decisions and discoveries
<New facts/trade-offs, reasons, and ADR references when warranted.>

### Outcome
<Coordinator acceptance decision and timestamp, or exact blocker.>

Use ISO 8601 timestamps with offsets for progress. Keep evidence concise and reproducible, with redacted excerpts or artifact paths rather than large logs. Distinguish planned commands from executed commands and unavailable checks from passing checks. Keep the plan self-contained by summarizing discoveries that affect the next task, not just linking to an agent conversation.

Validation gates

For Rust milestones, use the relevant focused tests and the formatting, Clippy, default tests, and release-build commands in AGENTS.md. Add exact commands when targets exist; do not invent passing outputs. Unit and fixture checks run without root. Use standard-library test adapters rather than an additional mock framework.

Container suites are opt-in and use pinned/cached Alpine images, fresh server/client containers, synthetic keys, isolated dual-stack networking, and scoped NET_ADMIN. The real runtime adapter executes inside the server container. Missing Docker/kernel/IPv6 prerequisites are incomplete validation, not success. Alpine fixture application is not a production systemd test.

Actual systemd/D-Bus acceptance runs in a disposable VM. Never use production to fill gaps in test coverage. Final release acceptance requires relevant unit, Alpine WireGuard, and VM systemd results reported separately. Document blockers without silently weakening the gate.

Milestone register

Each row is a delivery milestone, not a completion claim. Expand its record before execution; retain its original ID and acceptance scope.

ID Milestone Prerequisites State
1 Rust foundation, settings, and domain states None accepted
2 Server configuration editing with byte preservation 1 accepted
3 Client keys, encrypted vault, and existing-client import 2 accepted
4 Backups, safe Commit, and full restoration 3 accepted
5 Runtime observation and systemd restart adapter 4 pending
6 Client configuration delivery and reissue lifecycle 5 pending
7 Complete terminal management workflow 6 pending
8 Alpine Testcontainers integration 7; suitable disposable Docker environment pending
9 Real systemd acceptance and release readiness 8; disposable systemd VM pending

Milestone 1 — Rust foundation, settings, and domain states

Status: accepted (2026-10-18T18:59:35Z; independent R2 re-review and coordinator gates passed). Prerequisites: no previous milestone; verified GPT-6-Sol/medium coordinator and GPT-6-Luna/high workers, the pinned native Rust environment, and registry access or cached approved dependencies. Docker, root, a running VPN, and musl linking are not required for this milestone.

Outcome and scope

Deliver a compilable single-package foundation that can read validated settings relative to the executable, represent client identities and draft/saved/applied states, and allocate IPv4/IPv6 addresses without touching a real server. Startup supports --help and --version without root, settings, or vault access. Management startup checks root before attempting privileged operations.

This is a foundation, not a working VPN manager. Until later milestones wire in the TUI, management startup must explain that the management interface is not implemented and exit nonzero without reading a real server config or changing anything. Do not display a successful management session for a placeholder.

Read SPEC.md sections Purpose, Data locations, Data sources, New client parameters, and Operations and states; all three ADRs; and docs/ENVIRONMENT.md, docs/PROJECT_LAYOUT.md, docs/DEPENDENCIES.md, and docs/DEVELOPMENT.md. No WireGuard document edits, key generation, vault/import, backups/Commit writes, systemd/runtime adapters, QR, TUI, or container infrastructure are implemented here.

Context and implementation steps

At preparation, the repository contained design/Codex documents and rust-toolchain.toml, but no Cargo.toml, Cargo.lock, or source tree. The toolchain pin is 1.97.1 and the created manifest uses edition 2024. Local system tools match; rustup is absent. Musl standard library/linker readiness remains deferred to milestone 8.

Execute the tasks below sequentially with one implementation writer. For each task dispatch, the coordinator supplies exact document sections and the interface contract established by the prior task. Workers do not edit PLANS.md or accept work.

Task Owner Files and deliverable
M1.1 implementer Cargo.toml, Cargo.lock, src/main.rs, src/lib.rs, narrow startup/error modules as needed, README.md and .gitignore: package scaffolding, arguments, root boundary, safe incomplete-management behavior
M1.2 implementer src/config.rs, config/wireguard-admin.example.toml, settings tests: typed settings, validation, and executable-relative paths
M1.3 implementer src/domain.rs and child modules only if needed, domain tests: public-key identity, change states, address allocation and reservation rules
M1.4 reviewer, then repairer as needed Independently inspect the integrated M1 changes and acceptance evidence; coordinator assigns bounded repair files and finding IDs
M1.5 coordinator Run integrated gates, update outdated scaffolding statements, record evidence and acceptance in this plan

M1.1 creates package wireguard-admin, binary wireguard-admin, library wireguard_admin, edition 2024, and rust-version 1.97.1. Keep main thin, parse only the small agreed argument surface with std, and test the effective-UID boundary through an injectable/pure decision rather than requiring root tests. Unknown arguments fail clearly. Preserve existing documentation/configuration. Add narrow ignores for local deployment data without hiding config examples or synthetic fixtures. Do not create placeholder files for all future modules.

M1.2 uses TOML with explicit required fields for interface name, server config path, client source directory, server endpoint, IPv4 and IPv6 allocation ranges, DNS addresses, systemd unit, and nonzero restart timeout. Data root is optional and defaults to executable-directory/Backup with data and archives beneath it. Choose and document a fixed settings filename alongside the executable; do not add a new CLI settings override. Resolve relative configured paths consistently against that directory, never the process working directory. Separate parsing, validation, and resolution so tests do not read the host environment. Do not require referenced production files or systemd to exist to validate settings.

Validate address families, ranges, endpoint syntax, usable service/interface names, DNS values, and timeout. Do not resolve DNS over the network in a settings test. Endpoint validation must support a hostname or IP with a port and bracketed IPv6. Document the resulting settings schema in the example and README.

M1.3 represents a public key as a validated 32-byte identity with standard WireGuard Base64 parsing; duplicate aliases remain valid. Keep applied snapshots separate from saved and draft state. Model unavailable runtime state explicitly. Pass existing/server/reserved addresses as inputs; do not query real interfaces. Deletion from draft alone must not reserve an address as if saved; committed removal retains the address until an explicit successful-apply transition. Discarded drafts restore saved allocation availability. This milestone tests these transitions as pure model behavior, not an actual Commit implementation.

Allocate the first usable free address with bounded work proportional to the occupied set, not subnet size. Exclude supplied server/occupied/reserved addresses and IPv4 network/broadcast addresses where applicable. Handle /31, /32 and narrow IPv6 ranges explicitly, avoid arithmetic overflow, and return clear exhaustion errors. Manual addresses must match the configured family/range and be available. Do not silently add IPv6 to imported IPv4-only model records.

Approved direct dependencies for this milestone: serde (derive), toml, thiserror, ipnet, time when timestamps are needed, base64 for public-key representations, and rustix for the effective-UID query. Use tempfile as a dev-dependency for filesystem/subprocess fixtures if needed. Select compatible versions/features; no unapproved crate or premature crypto/TUI/container dependency. Generate the initial Cargo.lock once dependency choices are resolved; then use --locked.

Acceptance and validation

  • M1-A1: package/library/binary compile under the pinned native toolchain; Cargo.lock is tracked and no unrelated dependency or file scaffolding is added.
  • M1-A2: help/version work without root or settings, unknown arguments fail, and management startup rejects non-root before privileged access. Root-path behavior is covered by injection, without running the application as root.
  • M1-A3: valid TOML yields typed settings; missing/invalid fields produce actionable errors without opening a real server config, vault, or service.
  • M1-A4: executable-relative paths and defaults remain identical from two different working directories; test via pure path inputs or subprocesses, not parallel process-wide current-directory mutation.
  • M1-A5: valid/invalid public keys, repeated aliases, explicit unknown runtime state, draft discard, and saved/applied divergence have behavioral coverage.
  • M1-A6: IPv4/IPv6 first-free selection, manual selection, occupied/server exclusions, tiny ranges, exhaustion, and deferred release are verified. A large IPv6 range test demonstrates that allocation does not scan the entire subnet.
  • M1-A7: all standard gates pass without root/Docker/production access; a fresh independent reviewer has no unresolved actionable findings.
  • M1-A8: README describes the limited executable and setup honestly; commands, example settings, and source paths match actual implementation. Update the design-only statement in AGENTS.md when source scaffolding really exists.

Planned commands from the repository root after Cargo.toml and lockfile exist:

cargo metadata --locked --no-deps --format-version 1
cargo fmt --all -- --check
cargo clippy --locked --all-targets -- -D warnings
cargo test --locked
cargo build --locked --release
./target/release/wireguard-admin --help
./target/release/wireguard-admin --version

Expected: zero exits for these commands, successful help/version without settings, and all ordinary tests pass as an unprivileged user. Argument/root rejection and working-directory independence are exercised by tests. Record actual focused test commands when test names exist. Do not execute a bare management launch as root. No Alpine build, container tunnel, or VM/systemd check is claimed by M1 acceptance.

Progress

  • Preparation: toolchain configuration and environment observations recorded; this is not application implementation or milestone acceptance.
  • M1.1 — implementer (/root/implementer_m1_1), 2026-10-18T18:47:43Z; package and startup boundary integrated.
  • M1.2 — implementer (/root/implementer_m1_2), 2026-10-18T18:50:19Z; typed settings, validation, paths, example, and tests integrated.
  • M1.3 — implementer (/root/implementer_m1_3), 2026-10-18T18:54:21Z; domain model, allocation, and tests integrated.
  • M1.4 — fresh reviewer (/root/reviewer_m1_r1) R1 and R2, repairer (/root/repairer_m1_r1) for domain finding; all findings closed 2026-10-18T18:59:35Z.
  • M1.5 — coordinator integrated validation and acceptance completed 2026-10-18T18:59:35Z.

Review and repair

Finding Iteration Location and impact Owner Resolution and evidence State
M1-R1-01 R1/R2 src/domain.rs constructor can claim verified without a matching applied snapshot; misleading state repairer constructor rejects inconsistent Verified; Commit/mismatch demote stale Verified; 13 focused, 22 total tests passed; R2 confirmed closed
M1-R1-02 R1/R2 AGENTS.md still claims no Rust application exists coordinator opening statement updated to limited scaffold; R2 confirmed closed
M1-R1-03 R1/R2 Cargo.lock existed outside Git index coordinator git add -N Cargo.lock; git ls-files --error-unmatch Cargo.lock succeeds; R2 confirmed; no commit/content staging closed

Evidence and handoff

Preparation verified local rustc/Cargo 1.97.1, formatter and Clippy availability; rustup/musl readiness limitations are in docs/ENVIRONMENT.md. This execution verified the active coordinator turn as gpt-6-sol/medium from session turn context; project agent files and [agents] defaults specify gpt-6-luna/high. The repository had no Cargo manifest/source and a clean worktree at 2026-10-18T18:45:43Z. Current rustc/Cargo are 1.97.1, with rustfmt 1.9.0 and Clippy 0.1.97. No prior milestone exists. M1.1 added Cargo.toml, Cargo.lock, src/{main,lib,startup}.rs, and updated README/.gitignore. The coordinator inspected those files and the working tree. The worker reported passing locked metadata, fmt, Clippy, four startup tests, release build, release help/version, and diff check; integrated M1 gates remain pending. Cargo registry search failed, but approved rustix 1.1.5 resolved from local cache. M1.2 added src/config.rs, the example settings TOML, approved serde/toml/thiserror/ipnet dependencies, and README settings guidance. The coordinator inspected the files. The worker reported five focused settings tests and nine total tests passing, along with locked metadata, fmt, Clippy, release build, and diff check. Cargo resolution required an approved network-enabled retry after sandbox DNS failure. Independent review remains pending. M1.3 added src/domain.rs, its library declaration, and approved base64 dependency. The coordinator inspected the files. The worker reported 12 focused domain tests, 21 total tests, locked metadata, fmt, Clippy, and release build passing. The three implementer turns were independently verified from their session turn contexts as gpt-6-luna/high. Next: fresh review of the integrated milestone. R1 reviewer turn context was also verified as gpt-6-luna/high. R1 ran locked metadata, fmt, Clippy, 21 tests, release build, and help/version; all passed. It reported findings M1-R1-01 through M1-R1-03 above. No production, container, VM, or root management launch was used. Repairer turn context was verified as gpt-6-luna/high. Its domain repair passed 13 focused tests, 22 total tests, fmt, Clippy, and release build. The coordinator updated AGENTS.md and marked Cargo.lock intent-to-add in the Git index after a sandbox write denial and approved escalation. git ls-files now recognizes it; no Git commit was created. R2 re-review closed all three findings with no new actionable issues. Its turn context was verified as gpt-6-luna/high.

Coordinator integrated gates on 2026-10-18: cargo metadata --locked --no-deps --format-version 1, cargo fmt --all -- --check, cargo clippy --locked --all-targets -- -D warnings, cargo test --locked (22 passed), cargo build --locked --release, release --help, release --version, and git diff --check all exited 0. Unknown argument and a bare management launch as UID 1000 exited 2 with clear diagnostics, as expected. git ls-files --error-unmatch Cargo.lock exited 0; the file is intent-to-add in the Git index and its contents are not staged or committed. Unit/fixture evidence only; Alpine WireGuard and disposable systemd VM checks are later milestones and were not run. No production configuration, key, service, or VPN was accessed.

Decisions and discoveries

Use the already installed native compiler release as the reproducible baseline, not an unpinned stable channel. The toolchain file is consumed by rustup proxies; the current system-packaged binaries require explicit version checks. This is an environment constraint, not permission to install packages or alter global tools.

Outcome

Accepted 2026-10-18T18:59:35Z after independent review and integrated gates. The executable currently exposes help/version and safe startup errors; settings and domain operations are library logic. Management/TUI behavior remains for later milestones. Stop here; next milestone is 2 and has not started.

Milestone 2 — Server configuration editing with byte preservation

Status: accepted (2026-10-18T19:26:14Z; independent R3 review and integrated gates passed). Prerequisites: milestone 1 accepted; pinned native Rust environment and approved cached dependencies or registry access. No root, WireGuard, systemd, Docker, or production files are required.

Outcome and scope

Deliver a pure library API that parses an existing server configuration, inspects its client entries, and prepares additions, deletions, alias changes, and managed metadata updates entirely in memory. Rendering an unmodified parsed document must reproduce the original bytes exactly. Editing a client must preserve every byte outside the explicitly owned edit spans.

Governing requirements: SPEC.md Data sources, Operations and states, Importing existing clients, and configuration-preservation acceptance criteria; ADRs 0001 (no implicit apply) and 0003 (server authority); PROJECT_LAYOUT.md, DEVELOPMENT.md, DEPENDENCIES.md, and TESTING.md. Follow the existing model and module conventions instead of introducing a second client identity system.

Out of scope: reading production files, filesystem write/Commit orchestration, key generation, client-file scanning/import and cryptographic matching, vaults, backups, TUI, runtime/systemd actions, and network integration tests. Startup must retain the safe incomplete-management behavior from milestone 1.

Existing code and integration constraints

  • src/lib.rs currently exposes config, domain, and startup only.
  • domain::PublicKey parses canonical Base64 into 32 bytes and provides identity, equality, ordering, and formatting. Reuse it for lookup; never identify peers by alias or position alone. domain::validate_alias rejects empty/control text while allowing repeated aliases across clients.
  • domain::Client contains identity, alias, and ClientAddresses with mandatory IPv4 plus optional IPv6. Snapshot rejects duplicate public keys. The document representation must not force every existing [Peer] into these narrow types.
  • Existing peers may have multiple AllowedIPs, network prefixes, missing fields, duplicate keys, IPv6-only addresses, unknown parameters, or malformed metadata. Retain their original spans and return explicit inspection issues. Do not pick the first address, collapse routes, drop a peer, or invent missing values to force a successful conversion. Provide an explicit fallible projection for compatible records; keep unsupported records available for inspection.
  • Settings already expose resolved server paths but this milestone consumes supplied document content only. ClientStates models pure draft/saved/applied transitions; parsing/rendering must not mutate it or claim anything is applied.
  • Cargo already includes base64, ipnet, rustix, serde, thiserror, and toml. No generic INI parser or new unapproved dependency is needed. Approved time may be added for UTC timestamp validation; do not add crypto or TUI crates early.

Context and implementation steps

Run tasks sequentially with one writer. The coordinator supplies relevant requirements, acceptance IDs, owned files, and prior interface agreements to each worker. Use GPT-6-Luna/high implementer, a separate reviewer, and repairer under the GPT-6-Sol/medium coordinator. Workers do not edit milestone acceptance state.

Task Owner Files and deliverable
M2.1 implementer src/wireguard.rs, src/wireguard/document.rs, src/lib.rs, focused tests: lossless document/span representation and peer inspection
M2.2 implementer src/wireguard/metadata.rs, document integration, docs/CONFIG_FORMAT.md: versioned managed comment grammar and validated metadata
M2.3 implementer wireguard module and narrow domain adapters: atomic in-memory add/delete/rename/metadata preparation keyed by PublicKey
M2.4 implementer tests/config_preservation.rs, tests/fixtures/wireguard/, README/library usage documentation: integrated preservation and failure scenarios
M2.5 reviewer, repairer Independent integrated review, bounded fixes and regression checks, re-review
M2.6 coordinator Integrated gates, evidence/findings ledger, acceptance and next handoff in PLANS.md

M2.1 defines document ownership before editing. Preserve section order, whitespace, line endings (LF/CRLF, including mixed endings), blank lines, unknown directives, comments, and absence of a final newline. Use retained source bytes and spans, not reconstructed serialization or a map keyed by property name. Separate raw storage from parsed public views; errors and Debug must not expose the raw server text, Interface PrivateKey, or peer PresharedKey. Unknown/unparseable content is preserved, not silently normalized. Document supported input encoding; invalid text must either remain losslessly opaque or be rejected without lossy decoding.

Recognize WireGuard sections and key/value syntax deliberately. Repeated [Peer] sections are expected; repeated/conflicting identity fields or the same public key in multiple peers are ambiguous, not last-write-wins. Inspect them with issues and refuse edits whose target or boundaries cannot be established safely.

M2.2 specifies a uniquely recognizable managed block immediately before its peer, with begin/end markers, explicit format version, alias, and optional creation UTC timestamp. Before M2.3, document the exact grammar, escaping, ownership and attachment rules, supported versions, unknown-field policy, and examples in docs/CONFIG_FORMAT.md. Use unambiguous single-line encoding for aliases so #, =, quotes, Unicode, or marker-looking text cannot create directives/sections. Reject control/newline injection using domain validation. Reuse approved parsing facilities where useful; a TOML-encoded string within comments does not mean the server file itself is TOML. Never put secrets or a second public-key copy in the block. Existing peers with unknown creation date have no fabricated timestamp.

Metadata absence is a legacy state, not corruption. Use a shortened public-key fallback only in a public view; do not persist it as a chosen alias implicitly. Client filename fallback belongs to later import work. Malformed, duplicated, misattached, or unsupported-version blocks must yield explicit issues and block unsafe metadata edits; do not overwrite them as if absent or interpret arbitrary administrator comments as managed blocks.

M2.3 adds a new peer from validated public identity and supplied client addresses, not a newly generated key. New host routes use /32 and /128 as appropriate; existing AllowedIPs and connection parameters remain unchanged on alias edits. Allow IPv4-only existing clients, repeated aliases, and caller-supplied timestamps. Existing-peer metadata preparation is an explicit edit, not a side effect of parse. A rename changes only the owned alias representation (or inserts an explicit new managed block when safely absent), preserving any known creation date.

Deletion removes only the selected peer's owned section content and attached managed block. Define handling of administrator comments around and within the peer so deleting it never removes/reassigns another peer's managed metadata or unrelated comments. Sections such as [Interface], PostUp/PostDown, and arbitrary server directives must never be rewritten or executed. Unknown peer directives are retained on non-deletion edits; deletion deliberately removes the selected peer's configuration, not neighboring content.

Validate each edit completely before applying it. Missing/ambiguous target, invalid key/alias/address/metadata, or unsupported structures must leave the original document unchanged. Sequential edits recompute or otherwise maintain valid spans; no stale offsets after insertions/deletions. Rendering never writes files and must not invoke commit_draft, system tools, or runtime adapters.

M2.4 uses synthetic fixture data only. If secret-shaped input is required for preservation/redaction checks, generate dummy values in the test or clearly mark non-production placeholders; do not print full fixtures in assertion failures. Prefer comparisons of preserved slices and precise changed spans over brittle snapshots of the entire file. No real server sample is needed or permitted.

Acceptance and validation

  • M2-A1: unmodified round trips are byte-identical for LF, CRLF, mixed endings, no final newline, whitespace/comments, unknown directives, and multiple peers.
  • M2-A2: parse/inspection/edit/render operate only on caller-supplied data; no filesystem persistence, live config access, runtime mutation, or startup change.
  • M2-A3: public identity reuses PublicKey; duplicate aliases are supported; malformed/missing/duplicate keys and unsupported address shapes remain visible as issues rather than being silently normalized into Snapshot/Client.
  • M2-A4: new peer addition, selected-peer deletion, rename, and explicit legacy metadata insertion affect only intended spans; all server parameters and unrelated administrator comments remain byte-identical.
  • M2-A5: the exact metadata format is documented with supported version, escaping/attachment rules and examples. Missing creation dates stay missing; rename preserves known dates, and no key copy or secret appears in metadata.
  • M2-A6: malformed/unknown-version/misattached blocks and ambiguous peers are preserved and reported; unsafe edits fail without changing any original bytes.
  • M2-A7: boundary cases cover first/last/adjacent peer removal, inter-peer comments, Unicode/quoted/marker-like aliases, control injection, and repeated edits with no stale spans. Rendering an edit result and parsing it again produces the intended public records without modifying untouched content.
  • M2-A8: unsupported routes, multiple addresses and IPv6-only records round-trip unchanged; projection to the narrow M1 model fails explicitly when necessary.
  • M2-A9: errors/Debug never disclose secret-bearing source lines or complete documents; input rejection does not corrupt the caller's document.
  • M2-A10: existing M1 tests and startup help/version behavior remain intact; focused/full gates pass and independent review has no unresolved findings.

Planned commands from the repository root, after M2 targets exist:

cargo test --locked wireguard
cargo test --locked --test config_preservation
cargo fmt --all -- --check
cargo clippy --locked --all-targets -- -D warnings
cargo test --locked
cargo build --locked --release
./target/release/wireguard-admin --help
./target/release/wireguard-admin --version
git diff --check

Use unit-test names containing wireguard for the focused filter, and verify that the filter actually runs tests. All commands above should exit zero; negative cases are assertions inside passing tests, not ignored failures. Run as an unprivileged user with synthetic input. Dependency resolution is needed only if approved time is introduced; update Cargo.lock before --locked gates. Document the exact commands/results and test counts when execution occurs. No container or VM result is required or claimed for this pure-library milestone.

Progress

  • Description prepared from the current M1 code and approved documents.
  • M2.1 — implementer (/root/implementer_m2_1) completed 2026-10-18T19:10:30Z; lossless parser/inspection integrated.
  • M2.2 — implementer (/root/implementer_m2_2) completed 2026-10-18T19:14:23Z; v1 metadata grammar, attachment, and format document integrated.
  • M2.3 — implementer (/root/implementer_m2_3) completed 2026-10-18T19:17:05Z; pure peer edits/projection integrated.
  • M2.4 — implementer (/root/implementer_m2_4) completed 2026-10-18T19:20:05Z; fixture suite and README integrated with one retained failing assertion.
  • M2.5 — fresh reviewer (/root/reviewer_m2_r1) completed R1/R2/R3; repairer (/root/repairer_m2_r1) resolved both findings; R3 found no remaining issue.
  • M2.6 — coordinator integrated gates passed and acceptance recorded.

Review and repair

Finding Iteration Location and impact Owner Resolution and evidence State
M2-R1-01 (M2-I-01) implementation/R1 src/wireguard/edit.rs:214 rename rebuilt full metadata block and normalized mixed endings outside alias bytes repairer Replaced only the encoded alias value; mixed-ending regression and R2/R3 review passed closed
M2-R2-01 R2 PeerInspection.issues omitted valid but unsupported address shapes, contrary to M2-A3 repairer Added redacted unsupported-shape/missing-IPv4 issues and fixture assertions; R3 review passed closed

R1 found the preservation defect. R2 confirmed that fix and found the inspection gap. R3 confirmed both repairs and found no remaining actionable issue. The byte-preservation assertion was retained; unsupported records remain visible.

Evidence and handoff

Planning inspected Cargo.toml, src/lib.rs, domain public-key/client/state types, settings interfaces, M1 acceptance evidence, and governing documents. M1 records 22 passing tests and independent R2 acceptance; this planning task did not rerun those checks and makes no new application-validation claim. No M2 source edits, workers, Cargo commands, production files, containers, or services were involved.

Execution verified the active coordinator turn as gpt-6-sol/medium from session turn context. M1 is accepted. At execution start the only existing worktree change was this prepared M2 record in PLANS.md; no M2 source files existed. Rustc/Cargo 1.97.1, rustfmt 1.9.0, and Clippy 0.1.97 are available. M2.1 contract: a caller-supplied byte buffer with retained source/spans, a public peer inspection view keyed by the existing PublicKey, secret-free issues/errors/Debug, and exact unchanged-byte rendering. Invalid UTF-8 may be rejected with a redacted error. Editing remains for later tasks. Next action: dispatch M2.1. No M2 validation result is claimed yet. M2.1 added src/wireguard.rs, src/wireguard/document.rs, and a library declaration. The coordinator inspected the actual files. The parser retains a UTF-8 source buffer and byte spans, rejects invalid UTF-8 without echoing it, reports peer identity/route issues without losing raw content, and renders unchanged bytes exactly. The worker reported seven focused wireguard tests, 28 full tests, fmt, Clippy, release build, and diff check passing. Its turn context was verified as gpt-6-luna/high. Metadata grammar and edits remain unimplemented; next action is M2.2. M2.2 added src/wireguard/metadata.rs, integrated metadata inspection in the document module, and documented v1 grammar in docs/CONFIG_FORMAT.md. The coordinator inspected those files. Valid blocks expose alias, optional UTC creation time, and owned span; malformed/unsupported/misattached blocks yield redacted issues. The worker reported 14 focused wireguard tests, 35 full tests, fmt, Clippy, release build, and diff check passing; no new dependency. Its turn context was verified as gpt-6-luna/high. Next action is M2.3. M2.3 added src/wireguard/edit.rs and API exports for add/delete/rename, explicit legacy metadata insertion, and fallible projection. The coordinator inspected the code. The worker reported 21 focused wireguard tests, 42 full tests, fmt, Clippy, release build, and diff check passing. Its turn context was verified as gpt-6-luna/high. Next: M2.4 fixture-level behavioral tests and README updates, then independent review. M2.4 added tests/config_preservation.rs, a synthetic server fixture, and README library API guidance. The coordinator inspected those files. Focused integration results: eight passed, one failed at the mixed-ending metadata rename assertion (M2-I-01). Full cargo test --locked likewise has 42 unit tests passing but fails on that integration assertion. Fmt, Clippy, release build, and diff check passed. Its turn context was verified as gpt-6-luna/high. No acceptance gate is claimed passing while this fails. Next: fresh independent review of all M2 changes and the recorded defect. R1 reviewer turn context was verified as gpt-6-luna/high. It reproduced M2-R1-01, ran 21 focused wireguard tests passing, and found fmt, Clippy, release build, and diff check passing. Integrated test/full test remain red. Next: repair M2-R1-01, then R2 re-review. The repairer was configured as gpt-6-luna/high. First repair restricted an existing valid metadata rename to its encoded alias value. R2 independently confirmed the mixed-ending regression passed and identified M2-R2-01: valid unsupported routes were not reported in PeerInspection.issues. The second repair added redacted address-shape issues and their behavioral assertions, while retaining raw routes, safe metadata edits, and fallible projection. It also replaced a parser expect on user input with a redacted typed error. R3 independently verified both repairs and reported no further actionable finding. R2/R3 reviewer turns used the same independently spawned gpt-6-luna/high reviewer; it remained read-only throughout.

Coordinator integrated gates on the final M2 source, run unprivileged with synthetic input on 2026-10-18: cargo test --locked wireguard passed 21 tests; cargo test --locked --test config_preservation passed 9 tests; cargo fmt --all -- --check, cargo clippy --locked --all-targets -- -D warnings, cargo build --locked --release, and git diff --check exited zero; cargo test --locked passed 42 unit and 9 integration tests (0 doc tests); release --help and --version exited zero with the existing safe startup output. The focused filter was verified to execute 21 tests. No production files, host VPN/service, container, or systemd VM was accessed; real-system checks are not required for this pure-library milestone and are not claimed.

Decisions and discoveries

M1's ClientAddresses cannot represent every valid existing AllowedIPs layout. Keep a lossless document model plus explicit projection issues rather than broadening product scope or truncating existing settings. Metadata grammar and comment ownership must be settled and documented before edit implementation. No new ADR is required merely for moving parser code into the planned modules.

Outcome

Accepted after independent R3 review and all M2-A1–A10 checks. No M2 blockers remain. Stop here; milestone 3 is the next action only on a new request.

Milestone 3 — Client keys, encrypted vault, and existing-client import

Status: accepted (2026-10-19T20:08:32Z; independent R7 review and coordinator gates passed). Prerequisites: milestones 1 and 2 accepted; pinned native Rust environment and access to compatible approved cryptographic dependencies or a populated cache. No root, running WireGuard, systemd, Docker, VM, or production input is needed.

Outcome and scope

Deliver library operations for WireGuard client key generation, encrypted vault initialization, password-free encryption of new records, password-authorized secret retrieval, and safe reconciliation/import of existing client files. Reuse M1 identities/settings and M2 server inspection without rewriting originals.

Read SPEC.md Data sources, Encrypted vault, Importing existing clients, Connection delivery, and Backups and restoration; ADRs 0002 and 0003; CONFIG_FORMAT.md; DEPENDENCIES.md, DEVELOPMENT.md, and TESTING.md. Preserve the distinction between vault encryption, server secrets, and an unencrypted output file.

This milestone prepares versioned vault bytes and encrypted import results; filesystem persistence/Commit and its mandatory backups belong to milestone 4. Initial setup prepares encryption of valid existing files immediately in the same setup operation, but must not install a vault, rewrite the server, or bypass the later persistence gate. Use supplied buffers and an explicit read-only source adapter; no automatic scan at application startup. TUI/password prompts, delivery files/QR and first-delivery session orchestration belong to milestones 6 and 7. No runtime or service operations are introduced.

Existing code and integration constraints

  • M1 provides domain::PublicKey, Client, ClientAddresses, Snapshot, ClientStates, and validated/resolved settings, including the source directory and data paths. Public keys, not filenames or repeated aliases, identify secrets.
  • M2 exposes wireguard::ConfigDocument::parse, render, peers, sections, and source_slice, plus explicit add/delete/rename/legacy-metadata edits. PeerInspection provides identity candidates, AllowedIPs, metadata, and issues. Its raw source may contain secrets; never expose it through import reports.
  • project_client requires valid attached metadata and a supported address shape. Do not make successful projection a prerequisite for preserving or reconciling existing records. Missing metadata, IPv6-only peers, extra routes, and ambiguous keys must remain inspectable rather than being silently dropped or normalized.
  • Managed metadata is already v1: Base64url alias and optional strict UTC creation string in an attached comment block. Keep that grammar unchanged. The server alias wins; a filename/public-key fallback is display-only until an explicit metadata edit. Do not assign import time as an unknown creation date.
  • Server parsing is not client parsing: a client file's [Interface] PrivateKey identifies the client, while its [Peer] PublicKey normally identifies the server. Never use that server key as the client's identity.
  • Cargo currently contains base64, ipnet, rustix, serde, thiserror, and toml. Add only the approved crypto crates needed here: age, x25519-dalek, getrandom, secrecy, and zeroize; tempfile may be a dev-dependency for source-adapter tests. Verify current official APIs, maintained compatible versions, feature flags, and secret-type compatibility before choosing them. No custom cipher/KDF, OpenSSL subprocess, wg genkey, unapproved crate, or unrelated upgrade.

Context and implementation steps

Run these tasks sequentially with one writer. GPT-6-Sol/medium coordinates; GPT-6-Luna/high implements, reviews independently, and repairs. The coordinator supplies scope, owned files, relevant documents, accepted interfaces, checks, and required report contents. Workers do not accept milestones or edit PLANS.md.

Task Owner Files and deliverable
M3.1 implementer src/vault.rs, optional src/vault/keys.rs, src/lib.rs, Cargo files: secret-safe WireGuard key types, generation and matching
M3.2 implementer src/vault/ modules as needed, docs/VAULT_FORMAT.md: asymmetric vault setup, password-protected identity, versioned envelopes and load validation
M3.3 implementer src/vault/import.rs or a narrow import module: caller-buffer client parsing, reconciliation reports and encrypted import preparation
M3.4 implementer read-only configured-directory adapter, tests/vault_import.rs, synthetic temporary inputs and README guidance: initial setup/import workflow and failure coverage
M3.5 reviewer, repairer Independent crypto/use-of-library, import and integrated regression review; bounded fixes and re-review
M3.6 coordinator Integrated gates, findings/evidence and acceptance/handoff in PLANS.md

M3.1 wraps private keys/passwords in secret-aware containers with redacted or no Debug, no implicit Display/Serialize, and explicit exposure only at necessary cryptographic/encoding boundaries. Generate WireGuard-compatible private keys using OS randomness and derive their public key using x25519-dalek. Preserve canonical WireGuard Base64 on output; validate decoded length/encoding on input. Keep imported private-key bytes intact rather than replacing them with newly generated keys. Test generation/derivation and matching without leaking values in assertion failures. Handle random-source errors as recoverable errors.

M3.2 initializes a separate age recipient/identity pair, never reusing a WireGuard key. Store only the recipient and password-encrypted vault identity in the public serialized bundle; encrypt each client record to that recipient without requiring the password. Use the age library's established authenticated encryption and password protection; do not implement the cryptographic construction manually.

Document exact v1 vault/record formats, component filenames/layout intended for M4, version handling, public record identifiers, and size limits before encoding production data. Use serde/TOML or opaque age payloads as appropriate, without silently introducing a second serializer dependency. Ensure serialization cannot write plaintext secrets through a generic derive. Reject unknown versions and truncated/oversized structures with redacted typed errors.

Bind the decrypted payload to its expected client public key and vault context; derive its public key again and reject mislabelled/swapped records. On unlocking, verify the decrypted vault identity matches the declared recipient. Do not allow public envelope metadata alone to authorize a key's identity or reissue status. Do not invent cryptography to authenticate a plaintext index: document the limits of unauthenticated public metadata and perform the necessary checks after decrypt.

Each authorized retrieval accepts a fresh password, decrypts only for that operation, validates binding, and returns an explicit short-lived secret result. Do not cache an unlocked identity, retain the password between requests, or fall back to original plaintext files after wrong password, missing/corrupt records, or password loss. Keep cloning/exposure of secrets minimal; zeroize supported owned buffers on drop and document unavoidable library-managed copies without claiming all process memory can be erased. Password-policy/UI additions are not part of this milestone.

M3.3 parses supplied client content without generic lossy INI serialization. Require one unambiguous client private key and derive its PublicKey for matching. Validate client Address values while retaining enough information to report unsupported shapes and compare addresses rather than conflating client prefixes with server host routes. Preserve existing optional connection parameters needed later, including any valid preshared secret, inside the encrypted payload; do not introduce new MTU/keepalive/PSK settings or generation. Reject ambiguous or malformed secret fields rather than choosing the last occurrence. Do not run PostUp/hooks, resolve DNS, mutate routes, or execute text from imported files.

Reconcile against the authoritative M2 server inspection and provide explicit, secret-free results for server-only, file-only, matched, mismatched, invalid, and ambiguous/duplicate candidates. Missing/invalid managed metadata and narrow projection failures are represented separately from cryptographic key mismatch. Compare the client file's server peer key with the server identity when it can be determined safely; absent/ambiguous server identity is unresolved, not assumed valid. Do not infer identity from filenames or friendly aliases.

Matching key material may be encrypted even when address parameters conflict, but record/report that reissue is blocked pending explicit reconciliation. Do not truncate routes, overwrite server metadata, automatically add a file-only client to the VPN, or treat public filename fallback as authoritative. Duplicate files or existing vault entries must not cause silent overwrite or last-wins selection; preserve the prior record and return an explicit resolution issue. A repeat import without decryption must not claim plaintext equality it cannot verify. Define deterministic handling and tests rather than guessing equivalence.

M3.4 supplies a bounded read-only adapter for the configured source directory: regular files only, documented candidate selection, no recursive traversal or symlink following, bounded content sizes, and redacted per-file errors. A source file failure must not abort other valid imports; a directory-enumeration failure must be visible and must not be called a successful complete scan. Use safe file opening for privileged future callers; expanding rustix's approved feature set is permitted when required, but no new direct crate is implicitly approved.

Initial setup combines vault creation with attempted import of valid candidates. Return the encrypted prepared bundle and a report; invalid/conflicting candidates remain visible and originals are untouched. Explicit later import reuses the vault recipient without asking for a password or replacing the protected identity. Reissue authorization remains separate. Keep directory IO out of pure parsing and crypto tests; all adapter tests use synthetic temporary directories, never actual configured server/client paths. All writes in this milestone are test artifacts or explicit serialization buffers, not installed application state.

Acceptance and validation

  • M3-A1: generated WireGuard keys derive valid PublicKey values; imported key correspondence and malformed Base64/length handling are tested without tools.
  • M3-A2: vault setup uses an independent key pair and password-protected identity; serialized application vault data contains no plaintext private keys, passwords, or imported secret-bearing configurations.
  • M3-A3: new record encryption and later explicit import need only the public recipient, not a password/unlocked identity; loading ciphertext needs no password.
  • M3-A4: correct-password retrieval returns the selected secret; wrong/missing password, unknown versions, malformed/truncated ciphertext, recipient mismatch, and record relabelling fail without fallback or loss of the original bundle.
  • M3-A5: independent retrieval operations require fresh authorization, with no reusable unlocked identity/password exposed through ordinary vault APIs. Errors, reports and Debug are redacted; secret serialization is explicit and protected.
  • M3-A6: matching uses the public key derived from client Interface PrivateKey, not its server peer key or alias. Reports cover matched/server-only/file-only, conflicts, invalid files, duplicate/ambiguous keys and unsupported address shapes.
  • M3-A7: address/server-binding discrepancies remain explicit and block the authorized reissue path until resolved; a valid secret may still be encrypted. Duplicate imports never silently replace existing secrets or trusted metadata.
  • M3-A8: setup prepares encrypted imports from valid source files while skipping invalid candidates with explanations. Regular-file selection, limits, symlink rejection and enumeration failures are tested using temporary data only.
  • M3-A9: source files and original server bytes are unchanged; no metadata is implicitly inserted, no creation dates invented, and M2 parsing/editing guarantees remain intact. Missing metadata does not prevent encrypted preservation of a key.
  • M3-A10: serialized bundle round-trip preserves encrypted identity/records without decryption, ready for M4 backup/persistence; no filesystem installation, implicit Commit/runtime change, UI delivery or first-issue bypass is introduced.
  • M3-A11: standard integrated gates and M1/M2 regressions pass; independent review closes actionable crypto, import, secret-lifetime and boundary findings.

Planned commands from the repository root after M3 tests and lockfile exist:

cargo test --locked vault
cargo test --locked --test vault_import
cargo test --locked --test config_preservation
cargo fmt --all -- --check
cargo clippy --locked --all-targets -- -D warnings
cargo test --locked
cargo build --locked --release
./target/release/wireguard-admin --help
./target/release/wireguard-admin --version
git diff --check

Ensure the focused filter runs tests and record counts/results; all gates should exit zero. Resolve approved new dependencies/update the lockfile before locked checks. Keep password-encryption tests bounded while exercising real library crypto; do not weaken production KDF parameters simply to speed up tests. Use synthetic generated keys and no sensitive panic/assert output. No root, Docker, VM, actual WireGuard interface, or production config is required or claimed.

Progress

  • Description prepared against accepted M1/M2 code and current documents.
  • M3.1 — implementer (/root/implementer_m3_1) completed 2026-10-18T19:41:39Z; secret-safe WireGuard key generation, parse, derivation and matching.
  • M3.2 — implementer (/root/implementer_m3_2) completed 2026-10-18T19:46:09Z; versioned age-encrypted bundle, fresh-password retrieval, format documentation.
  • M3.3 — implementer (/root/implementer_m3_3) completed 2026-10-18T19:52:45Z; caller-buffer parse/reconciliation and authenticated import status.
  • M3.4 — implementer (/root/implementer_m3_4) completed 2026-10-18T19:58:55Z; bounded read-only directory adapter, initial setup/import flow and synthetic tests.
  • M3.5 — fresh reviewer (/root/reviewer_m3_r1) completed R1–R7; repairer (/root/repairer_m3_r1) resolved all findings; R7 found no actionable issue.
  • M3.6 — coordinator integrated gates passed and acceptance recorded.

Review and repair

Finding Iteration Location and impact Owner Resolution and evidence State
M3-R1-01 R1 src/vault.rs:134 Debug printed untrusted imported connection fields repairer Redacted imported payload Debug; sentinel regression and R2 passed closed
M3-R1-02 R1 src/vault/source.rs:110 directory path was reopened after secure FD open, allowing enumeration/read inventory mismatch repairer Enumerates through pinned rustix::fs::Dir; path-replacement regression and R2 passed closed
M3-R1-03 R1 src/vault/import.rs:409 unknown server identity was collapsed into a known key mismatch repairer Added Matches/Mismatch/Unresolved and blocked retrieval regression; R2 passed closed
M3-R1-04 R1 src/vault/source.rs:206 duplicate existing vault entry was generic encryption failure repairer Added equality-unverified duplicate issue; repeat-import regression and R2 passed closed
M3-R2-01 R2 docs/VAULT_FORMAT.md omitted intended component filename/layout for M4 repairer Documented intended data_directory/vault.toml without persistence; R3 confirmed closed
M3-R2-02 R2 Secret-bearing test assert_eq! output could disclose synthetic key/config bytes on failure repairer Replaced with redacted boolean assertions; R3 confirmed closed
M3-R3-01 R3 src/vault.rs:306 unsupported encrypted payload version reported as RecordMismatch, not actionable UnsupportedVersion repairer Split version/label checks; valid encrypted unsupported-version regression and R4 passed closed
M3-R4-01 R4 src/vault.rs:48 canonical Base64 comparison created a plaintext key String without zeroization repairer Wrapped in Zeroizing<String>; R5 confirmed closed
M3-R5-01 R5 src/vault.rs:38 Base64 decode could drop partially decoded key bytes on malformed input before zeroizing wrapper repairer Decode into zeroizing buffer from allocation onward; R6 confirmed closed
M3-R5-02 R5 src/vault.rs:287 identity UTF-8 conversion cloned decrypted identity into ordinary Vec, vulnerable on error repairer Borrow from zeroizing plaintext; R6 confirmed closed
M3-R6-01 R6 src/vault/import.rs:112 allocated ordinary directive String before knowing whether value was secret/ignored repairer Borrowed dispatch; zeroizing PrivateKey/PSK allocations, duplicate/error paths and regressions passed; R7 confirmed closed

The reviewer otherwise found crypto binding, fresh authorization and bounded scan behavior sound at fixture level. No finding may be closed by hiding a conflict, disabling tests, or relaxing encryption.

Evidence and handoff

Planning inspected Cargo.toml, exported M2 document/edit/metadata APIs, CONFIG_FORMAT.md, M1 model constraints, approved dependencies and ADR 0002. M2 records independent R3 acceptance, 42 unit plus 9 integration tests, and passing integrated gates. Those results are historical evidence; this planning task did not rerun application tests, select crypto versions, or add dependencies/source. No workers were dispatched and no source directory, private key, service, or network environment was accessed.

Next execution action: verify configured model roles and the current repository, review official APIs/compatibility for the approved cryptographic libraries, then dispatch M3.1 with the bounded task and acceptance criteria. Escalate unavailable packages as dependency/environment blockers rather than changing approved crypto.

Execution verified the coordinator session as gpt-6-sol/medium; M1 and M2 are accepted. At start, the only worktree change was this prepared M3 plan. Rustc/Cargo 1.97.1, rustfmt 1.9.0 and Clippy 0.1.97 match the pinned baseline. Official crate documentation was checked for age 0.12.1 (Rust 1.74 minimum, recipient and passphrase APIs, no default features), x25519-dalek, getrandom, and secrecy. age re-exports its compatible secrecy via age-core; use that re-export rather than adding a conflicting direct version. The local cache initially lacked age; a read-only cargo info age@0.12.1 failed under sandbox DNS restrictions, then succeeded with approved network escalation. No manifest or source was changed during this prerequisite check. Next: assign M3.1 to the configured implementer. M3.1 added vault::WireGuardPrivateKey using approved x25519-dalek 2.0.1, getrandom 0.3.4, and zeroize 1.9.0; no age/secrecy direct dependency was introduced prematurely. The coordinator inspected the new key API and manifest. The implementer ran cargo test --locked vault (3 tests), cargo test --locked (45 unit and 9 preservation integration tests), fmt, Clippy, release build and diff check successfully. Its turn context was verified gpt-6-luna/high. No production data or service was accessed. Next: M3.2 versioned age vault format and authorized retrieval on top of this key type. M3.2 added approved age 0.12.1, VaultBundle recipient encryption and passphrase-protected identity, bounded TOML loading, ciphertext/recipient/key binding checks, and docs/VAULT_FORMAT.md. The coordinator inspected source, format and manifest. The worker ran cargo test --locked vault (9 tests), cargo test --locked (51 unit and 9 preservation integration tests), fmt, Clippy, release build and diff check successfully; its turn context was verified gpt-6-luna/high. This is task progress, not integrated M3 acceptance. M3.3 must extend encrypted records to retain imported connection parameters and enforce duplicate/no-overwrite and conflict reissue semantics; independent review must scrutinize the current public reissue_blocked flag's authority and secret lifetime. Next: M3.3 caller-buffer import/reconciliation. M3.3 added src/vault/import.rs and extended the encrypted v1 record payload to retain imported connection parameters, including valid PSKs. It changed duplicate insertion to fail without overwrite and moved the conflict gate into the authenticated payload; the public flag is advisory. The coordinator inspected implementation and updated format documentation. The worker ran cargo test --locked vault (15 tests), cargo test --locked (57 unit and 9 preservation integration tests), fmt, Clippy, release build and diff check successfully; its turn context was verified gpt-6-luna/high. This remains pending independent review. Potential review focus: unresolved versus mismatched server identity classification, preservation and lifetime of source buffers, and absence of conflict-clearing bypass. Next: M3.4 bounded read-only directory adapter and setup/import workflow. M3.4 added src/vault/source.rs, tests/vault_import.rs, README guidance, approved tempfile 3.23.0 as a development dependency, and rustix fs/std features. It uses Linux openat2 no-symlink file opens, per-file/aggregate limits and incomplete-scan reporting. The coordinator inspected the adapter, fixture tests and manifest. The worker ran cargo test --locked vault (16 tests), cargo test --locked --test vault_import (5), cargo test --locked --test config_preservation (9), cargo test --locked (58 unit, 9 preservation and 5 vault-import tests), fmt, Clippy, release build and diff check successfully; its turn context was verified gpt-6-luna/high. No production path or service was accessed. Possible review focus: path-based directory enumeration after secure fd open, explicit duplicate-existing-record reporting, and whether server-key unknown is distinct from mismatch. Linux openat2 absence yields an incomplete scan, not success. Next: fresh integrated independent review M3.5 before acceptance. Fresh R1 reviewer turn used gpt-6-luna/high. It inspected integrated code and ran focused vault (16), vault-import (5), and M2 preservation (9) tests; the full suite passed 58 unit plus 14 integration tests, and fmt, Clippy, release build, help/version and diff check passed. It found M3-R1-01 through M3-R1-04 above. These passing tests do not close the review findings. Next: repair, re-review, then coordinator integrated gates. The repairer (/root/repairer_m3_r1, gpt-6-luna/high) reported all four R1 findings repaired with focused regressions. It ran cargo test --locked vault (18), vault-import (5), M2 preservation (9), full tests (60 unit plus 14 integration), fmt, Clippy, release build and diff check successfully. The coordinator inspected changed paths. Findings remain pending independent R2 re-review; no acceptance is claimed yet. R2 independently confirmed M3-R1-01 through M3-R1-04 and found no remaining defect in those paths. It found M3-R2-01 and M3-R2-02 above. Focused vault (18), vault-import (5), preservation (9), full tests (60 unit plus 14 integration), fmt, Clippy, release build, help/version and diff check passed; the passing gates do not resolve the documentation and test-output gaps. Next: narrow repair and R3 re-review. R3 independently closed M3-R2-01/02 and confirmed R1 repairs remain intact. It found M3-R3-01 and noted an M3-A7 evidence gap: the existing known server-key mismatch test also has unsupported address projection, so an isolated mismatch-blocks-reissue test is needed. Aggregate scan-byte and directory-entry limits are implemented but lack dedicated boundary tests; their coverage should be strengthened if practical. R3 reran focused vault (18), vault-import (5), preservation (9), full suite (60 unit plus 14 integration), fmt, Clippy, release build, help/version and diff check successfully. Passing checks do not close M3-R3-01. Next: repair and R4. The repairer (/root/repairer_m3_r1) split encrypted payload version and label checks, added a valid age-encrypted unsupported-version regression, an isolated known server-key-mismatch/reissue-block regression, and inexpensive aggregate-byte/directory-entry cap tests through private testable limits. It reported 22 focused vault tests, 5 vault-import tests, 9 M2 preservation tests, 64 unit plus 14 integration tests in the full suite, and passing fmt, Clippy, release build and diff check. The coordinator inspected the new code; R4 independent re-review is pending. No M3 acceptance is claimed yet. R4 independently confirmed M3-R3-01 and all earlier repairs. It reran focused vault (22), vault-import (5), M2 preservation (9), full tests (64 unit plus 14 integration), fmt, Clippy, release build, help/version and diff check, all passing. It found M3-R4-01 above; this must be repaired before M3 acceptance. No production access or real-system result is claimed. R5 independently confirmed M3-R4-01 but identified M3-R5-01/02 in adjacent secret-lifetime paths. Focused vault (22), vault-import (5), preservation (9), full tests (64 unit plus 14 integration), fmt, Clippy, release build, help/version and diff check all passed; these checks do not prove zeroization on failure. Earlier repairs remain intact. Next: targeted cleanup and R6 re-review; M3 is not accepted. R6 independently confirmed M3-R5-01/02: base64 decoding now occurs inside a zeroizing Vec even on failure, and age identity parsing borrows from zeroizing bytes. It found M3-R6-01 in the import parser. It reran focused vault (22), vault-import (5), M2 preservation (9), full suite (64 unit plus 14 integration), fmt, Clippy, release build, help/version and diff check, all passing. No production access or real-system result was claimed. Next: targeted parser cleanup and R7. The repairer changed src/vault/import.rs to borrow section, directive key and value until dispatch; ignored/misplaced values are not allocated. Recognized PrivateKey/PSK values enter zeroizing storage immediately, including duplicate/error paths. Redaction regressions were added. It reported focused vault (22), full tests (64 unit plus 14 integration), fmt, Clippy, release build and diff check passing; the coordinator inspected the changed parser. Independent R7 re-review was the next gate after this repair. R7 independent reviewer (configured gpt-6-luna/high) inspected the final parser, prior repairs, cryptographic/secret lifetimes, source-directory safety and M3-A1–A11. It found no remaining actionable issue and recommended acceptance. The coordinator reran the full planned command set on the final integrated source on 2026-10-19: cargo test --locked vault passed 22 tests; cargo test --locked --test vault_import passed 5; cargo test --locked --test config_preservation passed 9; cargo fmt --all -- --check, cargo clippy --locked --all-targets -- -D warnings, cargo build --locked --release, and git diff --check exited zero; cargo test --locked passed 64 unit tests, 9 preservation integration tests and 5 vault-import integration tests (0 doc tests); release --help and --version both exited zero with existing safe startup output. The focused vault filter ran 22 actual tests. These are unprivileged synthetic/unit fixture results only. No production files, running VPN, host service, Alpine container or systemd VM was accessed; container/VM checks are not required for this M3 library milestone and are not claimed. No M3 blocker remains.

Decisions and discoveries

The client's Interface private key and the server's Peer public key have different roles. Existing M2 source/span APIs support authoritative reconciliation but must not be reused as an unsafe source-dump parser for secrets. M1 projection cannot represent all legacy peers; use richer import inspection without changing that model or metadata v1 merely to suppress anomalies.

Prepared vault encoding is the M3/M4 interface: M3 supplies versioned encrypted components; M4 owns atomic installation, backup-before-mutation, restore, and Commit deletion. M6 owns file/QR generation and first-delivery session policy. This boundary avoids persisting unbacked state during an import/crypto milestone.

Outcome

Accepted after independent R7 review, closure of all findings and passing M3-A1–A11 gates. Stop here; milestone 4 is the next action only on a new request. No vault installation, Git commit, deployment or production access occurred.

Milestone 4 — Backups, safe Commit, and full restoration

Status: accepted (2026-10-19T21:10:25Z; independent R5 closed all findings and final coordinator gates passed). Prerequisites: milestones 1–3 accepted; pinned native Rust environment and available approved dependencies. This milestone uses synthetic temporary filesystems and injected failures, not root, production files, or running services.

Outcome and scope

Deliver library/application operations that install prepared M3 vault data, save an M2-edited server document through Commit, preserve internal allocation state, create/list backups, and preview/confirm full restoration without a vault password. Writes must be honest about partial failure and recoverable from the mandatory pre-operation backup. Neither Commit nor restore applies VPN changes.

Read SPEC.md Data locations, Data sources, Operations and states, Encrypted vault, and Backups and restoration; all three ADRs; VAULT_FORMAT.md, CONFIG_FORMAT.md, DEVELOPMENT.md, DEPENDENCIES.md, ENVIRONMENT.md, and TESTING.md. Distinguish the application's Commit action from Git commits, which are not authorized here.

Out of scope: TUI, output files/QR, password prompts, reissue reconciliation, real WireGuard/systemd observation or restart, automatic failed-restart rollback, concurrent editor detection/locking, compression, retention cleanup, migration to different server paths, and production access. The startup management placeholder remains safe until later milestones; expose testable APIs rather than a new CLI.

Existing code and integration constraints

  • config::ResolvedSettings provides executable-relative server/client/data paths and data/archive directories; settings live at wireguard-admin.toml beside the executable. Use explicit resolved paths in adapters, never process cwd.
  • M2 ConfigDocument and edits preserve original bytes and report unsupported peers. Commit persists the prepared document without reserializing server settings, silently projecting away anomalies, or rewriting unchanged comments.
  • M3 VaultBundle::to_toml/from_toml encode/load encrypted v1 bundles without password entry. VAULT_FORMAT.md fixes the intended path as data/vault.toml. Preserve recipient, encrypted identity, records, and authenticated blocked flags; backups/restore do not decrypt or rebuild them from plaintext imports.
  • PreparedImportSetup contains bundle/report; read-only scans never installed application state. M4 owns initial installation through the same persistence and pre-Commit backup boundary as later prepared imports.
  • Vault records are private and there is currently no public removal operation. Add a narrow public-key removal API as needed; do not serialize/reparse internals to bypass encapsulation or expose plaintext. Do not automatically purge file-only imported records merely because they are absent from the server configuration.
  • M1 ClientStates::commit_draft updates the in-memory saved snapshot and address reservations only. Invoke/reflect that transition after successful disk commit, never before it. Current reservation state is private and has no persistence codec; add explicit validated state encode/load support, not generic secret or whole-document serialization. Preserve unsupported M2 peers outside narrow Client.
  • Historical verified/applied snapshots are not fresh runtime observations. On reopening/restoring, preserve needed reservation/history data but mark current runtime unknown until M5 verifies it. Restore never frees a pending deleted IP.
  • Cargo already includes M1–M3 libraries and tempfile for tests. Approved tar and time may be introduced with minimal features, and tempfile/rustix used for secure writes. Moving tempfile to normal dependencies is allowed if needed. No new hash/UUID/compression/database/framework dependency is implicitly approved.

Context and implementation steps

Use sequential tasks and one writer. GPT-6-Sol/medium coordinates; GPT-6-Luna/high implements, reviews independently, and repairs. Supply explicit ownership, scope, accepted interfaces, validation, and task report requirements on each dispatch.

Task Owner Files and deliverable
M4.1 implementer src/backup.rs and child modules as needed, docs/BACKUP_FORMAT.md: versioned allowlisted archive, limits, inventory, validated preview
M4.2 implementer narrow persistence module, src/config.rs/src/domain.rs adapters as needed, docs/PERSISTENCE.md: safe path handling, validated state, staged writes and recovery contract
M4.3 implementer src/app.rs, src/vault.rs narrow deletion API, library exports: prepared change sets, initial setup/Commit and mandatory backup ordering
M4.4 implementer application/backup/persistence modules: manual and pre-restart backup APIs, previewed confirmed full restore, restart-history boundary
M4.5 implementer tests/commit_restore.rs, test-only failure adapters and README: round trips, fault injection, archive attacks, permission and reservation scenarios
M4.6 reviewer, repairer Independent integrated review, bounded repairs, re-review of failure paths and regressions
M4.7 coordinator Integrated gates, evidence/findings ledger and acceptance/handoff in PLANS.md

M4.1 defines a versioned uncompressed tar archive with a bounded TOML manifest. Use logical fixed member names for settings, server configuration, internal state, and encrypted vault; never restore directly to tar-supplied paths. Record UTC creation time, operation reason (manual/pre-Commit/pre-restore/pre-restart), member inventory/lengths, and historical verification status without inventing working state. Define collision-safe archive naming without overwriting an existing file. A new archive is visible in the list only when fully written and finalized.

Capture the currently installed saved bytes, not the proposed draft or decrypted records. Settings and server config may contain secrets: archives require 0600. Exclude source client files, drafts, previous archives, staging/journal artifacts, and delivered plaintext output. Initial setup can lack vault/internal state: represent genuine absence explicitly, not as corruption or an empty fake vault. Restoring that backup must restore absence for those owned components. Missing required settings/server config or read errors are not optional absence.

Bound archive/member size and entry count before reading/extracting everything. Reject unsupported manifest versions, missing/duplicate/unexpected members, absolute or traversal paths, links, device/special entries, inconsistent sizes, truncation, and unsafe extension/header interpretations. Parse allowed files in staging before mutation using existing settings/document/vault validators. Do not reject a losslessly supported anomalous peer simply because project_client fails. The tar checksum is not authentication; do not advertise tamper-proof backups or silently add a signing/hash dependency. Bounds/structural validation remain required.

M4.2 documents file layout and a versioned internal-state schema for reservations, historical apply evidence, and operation recovery if needed. Keep aliases and client configuration authoritative in the server file. Reject unknown or malformed state rather than silently losing reservations. A file loaded from disk must not self-assert current RuntimeObservation::Verified without runtime verification.

Separate orchestration from a narrow filesystem adapter for deterministic errors. Use safe no-follow/anchored path operations, regular-file checks and private staging. Detect target aliasing/overlap (for example vault and server resolving to the same file) before mutations. Do not traverse a symlink or use archive metadata to chmod/chown arbitrary targets. Create files/directories with 0600/0700 from creation; preserve protected directory access and handle existing permissions explicitly. Do not recursively delete the data directory to implement restore.

Use same-filesystem temporary files, complete writes, flush/sync, atomic rename, and directory sync where applicable. A rename is not a transaction across server, settings, and data directories. Document ordering, durable recovery markers or an explicit equivalent, and recovery from interruption between replacements before implementing it. Never claim multi-file atomicity just because each rename is atomic. Backup-before-mutation and write recovery are not optimistic locking.

On replacement failure, either leave the old set intact or return an explicit incomplete/recovery-required result with the preserved pre-operation backup. Do not publish success, advance saved memory, or accept another mutation over unresolved partial state. Recovery must be deterministic and tested after reopening from partial files. It may finish/restore a local write transaction as documented; it must never apply runtime changes or become automatic failed-restart rollback. Report cleanup failures honestly and keep recoverable evidence if cleanup is unsafe.

M4.3 builds a prepared change set containing edited server bytes, staged encrypted vault, and validated internal state. Preview/validate it before installation. Complete the pre-Commit backup before the first protected destination replacement. Failure to create/finalize the backup stops Commit and preserves installed bytes and saved in-memory state. Then stage/install the set and only after success advance saved snapshots and expose successful Commit. A failed Commit retains retryable draft intent without treating possibly partial disk state as consistent.

Deletion removes the explicitly selected client record and vault secret in the same committed operation; unknown/missing vault records need clear behavior, since server-only clients are legitimate. Unrelated imported/file-only records remain. Earlier archives remain unchanged. Prepared imports/setup never overwrite an existing vault identity silently and never clear authenticated reissue blocking. Discarding an uncommitted preparation must not change installed files. Persistent settings updates are not a new product feature; Commit handles agreed client and internal changes while settings are included in backups/restoration.

M4.4 exposes listing and manual backup plus a reusable pre-restart backup operation for M5, without restarting anything here. Sorting/time labels do not imply a backup is working. Accept historical verification only from an explicit verified-apply boundary intended for M5; represent no verified state honestly. Any identity/token used to associate a saved generation with verification must not be a fresh runtime claim or a new crypto dependency. Never mark pre-restart bytes known-working based on the backup reason alone.

Restore has a read-only preview followed by explicit confirmation of that prepared archive. Validate the chosen content and determine affected files before writes; then complete the precautionary backup, stage all replacement/deletion actions, and install. Restore settings, whole server file, internal state, and opaque vault as a complete saved set, not a client merge. Whole-file restoration of the archived server is intentional; ordinary Commit still obeys M2 client-only editing rules.

Use current trusted destination mapping and enforce the installation's invariant paths. Archived settings cannot redirect writes outside that mapping or switch data roots during restore; report incompatible paths instead of adding migration logic. Keep the selected validated archive stable between preview and confirmation so confirmation cannot apply different bytes. Do not silently discard the active draft until confirmed; cancellation/validation/backup failure preserves it. Successful restore discards it and reloads the restored saved state, retaining conservative pending reservations across the still-unchanged runtime boundary. Historical restored state cannot assert the current interface matches it. Caller never needs or supplies a password for archive listing, backup, preview or restore.

M4.5 injects failures at backup creation/write/finalization, staging writes, sync, each destination replacement, recovery and cleanup. Verify exact file sets and memory transitions rather than only error messages. Avoid permission tests that pass accidentally as root; run unprivileged and inject deterministic failures. Use temporary private directories and generated encrypted bundles. Do not log server fixture contents or decrypted data in failing assertions.

Acceptance and validation

  • M4-A1: documented versioned archive/state layouts round-trip settings, server bytes, encrypted vault and internal state without any password/decryption; initial missing vault/state is represented and restored explicitly.
  • M4-A2: complete backups precede Commit/restore and the future restart boundary; backup/read/finalization failure prevents protected replacements and successful memory transitions. Manual backup/listing works without mutations to saved data.
  • M4-A3: successful Commit writes the prepared client changes, vault and state; untouched server spans remain identical; saved memory advances only on success, runtime stays unchanged/unknown, and draft discard does not write files.
  • M4-A4: explicit client deletion removes only its current vault record while server-only and file-only anomalies remain supported; old archives are unchanged.
  • M4-A5: confirmed restore fully replaces owned saved components, including removal of components absent in the archive, without merging or reading plaintext client sources. Preview/cancel does not mutate or silently discard the draft.
  • M4-A6: original client source files, unrelated files and all archives outside the selected/pre-operation artifacts are protected from traversal, symlink/link escapes, target aliasing and unsafe tar entries; test bounded parsing and rejection.
  • M4-A7: 0600 secret-bearing files/archives and 0700 private directories apply from creation, including staging; errors and Debug redact secret-bearing bytes.
  • M4-A8: injected failures/interruption between multi-file replacements produce no false success or premature saved-memory change; reopening detects incomplete work and follows the documented deterministic recovery path before new mutations.
  • M4-A9: saved/deleted address reservations survive reopen and restore across unapplied changes; neither action releases them or trusts historical verification as current runtime. Missing/malformed state cannot silently permit address reuse.
  • M4-A10: listing records UTC reason and explicit historical apply status; pre-Commit/pre-restart archives and newest archives are not automatically working.
  • M4-A11: incompatible archived destination settings are rejected; restored server/config/vault stay at trusted invariant paths, and no whole-directory wipe or recursive archive inclusion occurs.
  • M4-A12: M1–M3 regressions and integrated gates pass; independent review closes actionable persistence, archive, backup-order and failure-recovery findings.

Planned commands from the repository root after M4 targets and lockfile exist:

cargo test --locked backup
cargo test --locked persistence
cargo test --locked --test commit_restore
cargo test --locked --test vault_import
cargo test --locked --test config_preservation
cargo fmt --all -- --check
cargo clippy --locked --all-targets -- -D warnings
cargo test --locked
cargo build --locked --release
./target/release/wireguard-admin --help
./target/release/wireguard-admin --version
git diff --check

Filters must execute actual focused tests. All commands should exit zero; negative cases are assertions within passing tests. Resolve approved added crates and update Cargo.lock before locked checks. Record actual counts, commands and failure-injection coverage. Tests are unprivileged filesystem/library checks only; no container/VM or live restart acceptance is required or claimed in this milestone.

Progress

  • Description prepared from accepted M1–M3 interfaces and current documents.
  • M4.1 — /root/implementer_m4_1 added src/backup.rs, docs/BACKUP_FORMAT.md, approved tar/time dependencies, and six focused archive tests (2026-10-19T20:23:23Z). Coordinator inspected the actual code; final integrated review and gates subsequently passed.
  • M4.2 — /root/implementer_m4_2 added src/persistence.rs, docs/PERSISTENCE.md, and validated reservation restoration in src/domain.rs (2026-10-19T20:30:34Z). Integrated review subsequently passed.
  • M4.3 — /root/implementer_m4_3 added src/app.rs, a narrow vault record-removal API, and one trusted archive-directory accessor (2026-10-19T20:35:58Z). Commit backup/install ordering and draft tests passed integrated review.
  • M4.4 — /root/implementer_m4_4 added list/manual/pre-restart backup boundaries and byte-bound restore preview/confirmation, with path-invariant validation and focused tests (2026-10-19T20:42:20Z); integrated review passed.
  • M4.5 — /root/implementer_m4_5 added nine cross-module tests and fixed restore reservations plus interface-name invariant (2026-10-19T20:49:14Z). Documentation was synchronized; independent review passed.
  • M4.6 — fresh /root/reviewer_m4_r1 reviewed the integrated result; separate /root/repairer_m4_r1 repaired R1 and R4 findings; R2/R3/R5 re-review closed all with no new actionable findings (2026-10-19T21:10:25Z).
  • M4.7 — coordinator ran all planned integrated gates and accepted M4 (2026-10-19T21:10:25Z).

Review and repair

Finding Iteration Location and impact Owner Resolution and evidence State
M4-R1-01 R1/R2 src/persistence.rs saved reads accepted existing secret-bearing files with broad permissions. /root/repairer_m4_r1 R2 confirmed fail-closed server/vault mode checks and regression tests. closed
M4-R1-02 R1/R2/R3 Archive and installation failure paths lacked deterministic evidence. /root/repairer_m4_r1 R3 confirmed partial restore interruption after server replacement, unchanged session saved/draft/applied state, failed rollback and fresh reopen recovery, and intact pre-restore archive; focused test passed. closed
M4-R4-01 R4/R5 src/app.rs lost verified saved-generation history on reopen and mislabeled pre-Commit backups. /root/repairer_m4_r1 R5 confirmed durable history after mandatory backup, truthful archive labeling, reopen history with runtime Unknown, and failure/clear tests. closed

R1 independently inspected the integrated code; cargo test --locked --test commit_restore passed 9 tests and git diff --check was clean. Reviewer did not claim Alpine, VM, live WireGuard, or host-service evidence. R2/R3/R5 completed the required re-reviews and closed all findings.

Evidence and handoff

Planning inspected Cargo.toml, M3 vault serialization and source/import APIs, VAULT_FORMAT.md, M1 state methods, M3 recorded acceptance and governing documents. M3 records independent R7 review, 64 unit tests plus 9 preservation and 5 vault import integration tests, and passing coordinator gates. These are historical results; this planning task did not rerun tests or modify application code, dependencies, files in configured runtime directories, or installed vault state.

M4.1 worker reported cargo test --locked --offline (70 unit, 9 preservation, 5 vault-import), cargo test --locked --offline backup (6), Clippy, format, and git diff --check passing. These are worker results, not final integrated acceptance. The archive API accepts explicit saved bytes and validates a bounded fixed-member UStar format without extraction; app-level semantic validation and trusted path handling remain for M4.2–M4.4. M4.2 worker reported 10 persistence tests, full suite (80 unit + 9 preservation + 5 vault-import), Clippy, release build, format and diff checks passing. It supplies SavedStore with validated read/install/recover, a private journal, and explicit recovery-required results; per-syscall fault injection remains for M4.5. These are worker results only. M4.3 worker reported formatting, Clippy, full tests (89 unit + 14 integration), release build, and diff checks passing. Its Application stages public-key edits, pre-Commit backups exact installed bytes, and advances saved memory only after install. Broad-route legacy deletion fails closed because the persisted reservation format holds single addresses; exact-host legacy routes are retained. This is worker evidence, not integrated acceptance. M4.4 worker reported 95 unit + 14 integration tests, formatting, Clippy, offline release build and diff checks passing. Coordinator inspection identified additional reservation/path failure cases for M4.5 tests and independent review. M4.5 worker reported 95 unit + 23 integration tests, focused backup (11), persistence (10), and commit_restore (9), plus format, Clippy, release build, help/version, and diff checks passing. Its tests reproduced and fixed restore dropping current saved-client reservations and accepting an archived interface-name switch. Syscall-level write/fsync fault injection is not yet exhaustive; this remains an explicit review concern. These are worker results, not final acceptance. Next action: fresh independent M4 reviewer, then repair/re-review as needed. R1 returned M4-R1-01 and M4-R1-02 as actionable. Next action: a separate repairer fixes those findings, then the reviewer rechecks. Repairer reported both fixed, with 102 unit + 23 integration tests and formatting, Clippy, release build and diff checks passing (2026-10-19T20:57:23Z). The coordinator has not yet accepted those claims; next action is independent R2 re-review. R2 (2026-10-19T20:58:52Z) closed M4-R1-01 and partially closed M4-R1-02. Focused persistence (15), backup (12 unit + 2 integration), commit_restore (9), and injected Commit test (1) passed. Reviewer requires a restore partial-install failure/recovery test at the application boundary. Next action: targeted repair and R3 re-review; milestone remains in progress. Targeted repair added a private restore-install test seam and regression. R3 (2026-10-19T21:01:08Z) closed M4-R1-02, found no new actionable defects, and passed the focused test plus git diff --check. Next action: coordinator integrated gates and A1–A12 decision. Coordinator gates passed on the R3 revision, but a targeted historical-status audit found M4-R4-01 (2026-10-19T21:03:13Z). Those gate results will be rerun after repair. The milestone remains in review; next action is targeted repair and independent re-review.

Final integrated evidence after R5 repair (coordinator, 2026-10-19T21:10:25Z): cargo test --locked backup passed 14 unit + 2 integration matches; cargo test --locked persistence passed 15; cargo test --locked --test commit_restore passed 9; cargo test --locked --test vault_import passed 5; cargo test --locked --test config_preservation passed 9. cargo fmt --all -- --check, cargo clippy --locked --all-targets -- -D warnings, cargo test --locked (108 library unit + 9 Commit/restore + 9 preservation + 5 vault import, 0 doctests), cargo build --locked --release, release --help and --version, and git diff --check all exited zero. All three findings are closed by independent R2/R3/R5; R5 found no new actionable issue. Evidence is unprivileged synthetic/unit-fixture only. No Alpine, disposable systemd VM, live VPN, host service, production file or production key was accessed or claimed. The terminal workflow and real runtime/systemd checks belong to later milestones. M4 has no remaining blocker.

Decisions and discoveries

Vault v1 already fixes data/vault.toml and authenticated conflict behavior. Preserve that format and opaque ciphertext. New state/backup formats belong in their own documents rather than altering vault or managed-comment v1 without need.

The persistence layer spans directories and cannot assume a single atomic rename. Interruption recovery and conservative address reservations are required write integrity, not concurrent editing support or automatic failed-restart rollback. Confirmed archive restoration replaces server settings as part of explicit recovery; normal client Commit continues to preserve administrator-owned bytes. The v1 internal-state historical timestamp follows the application's single-administrator save boundary; it is not a digest check against manual out-of-band file changes. Reopen treats history as historical, not current runtime verification. Restoration carries forward pending address reservations for safety while replacing archived client/settings/vault saved content.

Outcome

Accepted after independent R5 review, closure of M4-R1-01/M4-R1-02/M4-R4-01, and all M4-A1–A12 gates. Stop after M4. Begin M5 only on a separate request; no Git commit, deployment or production access occurred.

Milestone 5 — Runtime observation and systemd restart adapter

Implement wireguard-uapi runtime queries and the zbus systemd adapter behind narrow interfaces. Track service job completion and timeout, then verify interface/client correspondence before recording applied state and releasing addresses. Require backup before restart.

Acceptance: ordinary tests exercise successful and failed jobs, missing/mismatched interfaces, unavailable statistics, and no automatic rollback using controlled adapters. Real runtime/systemd acceptance remains explicitly pending until milestones 8 and 9; mocks cannot close those later gates.

Milestone 6 — Client configuration delivery and reissue lifecycle

Generate full-tunnel IPv4/IPv6 configurations using settings and preserved existing parameters where applicable. Save a root-only plaintext file and optionally render a matching QR code. Enforce post-Commit initial delivery and per-client password-based reissue with session-scoped retry after file errors.

Acceptance: file/QR payloads agree; no delivery before Commit; output failure permits directory change/retry; successful initial delivery or session exit ends password-free eligibility; overwrite requires confirmation; unrelated plaintext imports cannot bypass authentication.

Milestone 7 — Complete terminal management workflow

Connect tested application operations to Ratatui/Crossterm screens: list and statistics, forms, anomalies/import, draft/diff, Commit, restart, output/QR, and backup selection/restore. Keep expensive work off the render/input loop and maintain public-key-based selection.

Acceptance: keyboard workflows and cancellation preserve the required states, statistics refresh without claiming online status, destructive actions identify targets, passwords are masked, and terminal cleanup/resize/non-TTY/error behavior is verified. No implicit apply or save on exit.

Milestone 8 — Alpine Testcontainers integration

Implement opt-in Rust container tests and a minimal pinned Alpine image with wireguard-tools and iproute2. Cache images, use fresh isolated resources, build an Alpine-compatible test executable (prefer musl), and exercise the real runtime adapter within the server container. Avoid new helpers outside approved dependencies without agreement.

Acceptance: prerequisite checks and cleanup work; real dual-stack tunnels produce handshake/counter changes; creation/Commit leaves runtime unchanged until explicit test-only application; deletion retains access/address reservation until application; restored saved state does not implicitly apply. No host network, host D-Bus, production mounts, privileged default, or automatic module loading. Systemd coverage is explicitly excluded.

Milestone 9 — Real systemd acceptance and release readiness

Prepare a repeatable disposable VM procedure and run the actual application/systemd adapter against synthetic WireGuard state. Finish operating/recovery documentation, safe settings examples, native target release build, and review of executable runtime dependencies.

Acceptance: real D-Bus job handling, separate restart verification, address release, and manual backup recovery pass in the VM. All specification acceptance criteria have linked evidence. Required default checks and Alpine tests pass on the integrated revision; unresolved defects or unavailable required VM checks prevent final acceptance. Production is never the test environment.

Current progress and resume point

  • Milestones 1–4 are accepted; milestones 5–9 remain pending.
  • The approved dependency list is in DEPENDENCIES.md; container testing methodology is in TESTING.md.
  • Milestone 1 is expanded and the Rust environment is pinned.
  • Milestone 2 delivered a lossless in-memory WireGuard configuration editor, versioned metadata, and behavioral preservation tests.
  • Milestone 3 delivered secret-safe key handling, a versioned encrypted vault, caller-buffer reconciliation, and a bounded read-only source-directory import adapter.
  • Milestone 4 delivered versioned private backups, journaled saved-file Commit and recovery, previewed full restore, durable address reservations and historical verification bookkeeping, with independent R5 closure and coordinator gates.
  • Next action: stop after accepted M4; begin M5 only if separately requested.
  • M1–M4 integrated gates and independent reviews passed. No Alpine or VM results are claimed.

Plan maintenance

Update progress, findings, decisions, blockers, and the next action after each meaningful execution/review cycle and before handing off or ending a session. Preserve acceptance evidence when summarizing old iterations. Append scope changes with their reason; update the specification only when the user actually authorizes a requirement change.

Planning reference: OpenAI — Using PLANS.md for multi-hour problem solving. The specific model roles, milestone numbering, and review policy above are project decisions requested by the user.