# 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](AGENTS.md), [the specification](docs/SPEC.md), [architectural decisions](docs/adr/), [implementation order](docs/IMPLEMENTATION.md), and [the glossary](CONTEXT.md) before execution. The specification defines behavior; this plan schedules its delivery. Approved dependencies are in [DEPENDENCIES.md](docs/DEPENDENCIES.md); Alpine/VM testing rules are in [TESTING.md](docs/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. ```markdown ## Milestone — Status: pending Prerequisites: ### Outcome and scope ### Context and implementation steps ### Acceptance and validation - [ ] - [ ] Commands: ### Progress - [ ] ### Review and repair | Finding | Iteration | Location and impact | Owner | Resolution and evidence | State | | --- | --- | --- | --- | --- | --- | ### Evidence and handoff ### Decisions and discoveries ### Outcome ``` 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 | pending | | 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 - [x] M1-A1: package/library/binary compile under the pinned native toolchain; Cargo.lock is tracked and no unrelated dependency or file scaffolding is added. - [x] 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. - [x] M1-A3: valid TOML yields typed settings; missing/invalid fields produce actionable errors without opening a real server config, vault, or service. - [x] 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. - [x] M1-A5: valid/invalid public keys, repeated aliases, explicit unknown runtime state, draft discard, and saved/applied divergence have behavioral coverage. - [x] 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. - [x] M1-A7: all standard gates pass without root/Docker/production access; a fresh independent reviewer has no unresolved actionable findings. - [x] 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: ```sh 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 - [x] Preparation: toolchain configuration and environment observations recorded; this is not application implementation or milestone acceptance. - [x] M1.1 — implementer (`/root/implementer_m1_1`), 2026-10-18T18:47:43Z; package and startup boundary integrated. - [x] M1.2 — implementer (`/root/implementer_m1_2`), 2026-10-18T18:50:19Z; typed settings, validation, paths, example, and tests integrated. - [x] M1.3 — implementer (`/root/implementer_m1_3`), 2026-10-18T18:54:21Z; domain model, allocation, and tests integrated. - [x] 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. - [x] 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 Implement document-preserving parsing/editing and versioned metadata comments. Server configuration remains authoritative, duplicate aliases are permitted, and only managed client portions change. Acceptance: synthetic fixtures prove byte preservation of untouched settings/comments, client addition/deletion/rename, unknown-field preservation, missing creation dates, and rejection of metadata/terminal injection. No complete generic INI reserialization. ### 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 - Milestone 1 is accepted; milestones 2–9 remain pending. - The approved dependency list is in [DEPENDENCIES.md](docs/DEPENDENCIES.md); container testing methodology is in [TESTING.md](docs/TESTING.md). - Milestone 1 is expanded and the Rust environment is pinned. - Next action, only on a later request: prepare Milestone 2; no work on it has started. - 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](https://developers.openai.com/cookbook/articles/codex_exec_plans). The specific model roles, milestone numbering, and review policy above are project decisions requested by the user.