50 KiB
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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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 | needs_changes |
| 3 | Client keys, encrypted vault, and existing-client import | 2 | pending |
| 4 | Backups, safe Commit, and full restoration | 3 | pending |
| 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.rscurrently exposesconfig,domain, andstartuponly.domain::PublicKeyparses 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_aliasrejects empty/control text while allowing repeated aliases across clients.domain::Clientcontains identity, alias, andClientAddresseswith mandatory IPv4 plus optional IPv6.Snapshotrejects 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.
ClientStatesmodels 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
timemay 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
Implement separate vault/client key pairs, recipient encryption, password-protected vault identity, and versioned secret storage. Import from the configured directory on initial setup without deleting originals. Verify key correspondence and classify anomalies; conflicts block reissue until resolved.
Acceptance: new secrets encrypt without password entry, correct/incorrect password and corruption paths are covered, original files are unchanged, duplicates/conflicts are represented correctly, and persisted application data contains no plaintext client private keys. WireGuard key handling uses approved libraries, not utility subprocesses.
Milestone 4 — Backups, safe Commit, and full restoration
Implement versioned tar archives/manifests, mandatory pre-operation backups, internal state persistence, Commit orchestration, and previewed full restoration without decryption. Define partial-write handling and archive validation. Persist address reservations and verified-state bookkeeping as needed.
Acceptance: backup failure prevents protected mutations; Commit/restore never change the running VPN; archive composition matches the specification; malformed/traversal/link entries are rejected; restore returns saved settings/configuration/vault without a password. Tests demonstrate draft cancellation and deletion of current secrets while older archives remain unchanged.
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 and 2 are accepted; milestones 3–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.
- Next action: stop after accepted M2; begin M3 only if separately requested.
- M1 integrated gates and independent re-review passed. No container 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.