Add integration tests based on testcontainers
This commit is contained in:
@@ -7,7 +7,7 @@ These instructions apply to the entire repository. Keep documentation, code comm
|
|||||||
The project contains a Rust package with startup, settings validation, a pure client domain model, a byte-preserving WireGuard configuration editor, encrypted vault and read-only import APIs, saved-file persistence with recovery, backups, Commit, previewed restore, a runtime observation/restart adapter, and a terminal management interface under Milestone 7 review. Real WireGuard and systemd integration remain for Milestones 8–9. Do not report planned modules, tests, or commands as already working.
|
The project contains a Rust package with startup, settings validation, a pure client domain model, a byte-preserving WireGuard configuration editor, encrypted vault and read-only import APIs, saved-file persistence with recovery, backups, Commit, previewed restore, a runtime observation/restart adapter, and a terminal management interface under Milestone 7 review. Real WireGuard and systemd integration remain for Milestones 8–9. Do not report planned modules, tests, or commands as already working.
|
||||||
|
|
||||||
- Read [SPEC.md](docs/SPEC.md) and [CONTEXT.md](CONTEXT.md) before implementing behavior. The approved specification is the product contract; do not change it to make tests pass.
|
- Read [SPEC.md](docs/SPEC.md) and [CONTEXT.md](CONTEXT.md) before implementing behavior. The approved specification is the product contract; do not change it to make tests pass.
|
||||||
- For milestone execution, read and maintain [PLANS.md](PLANS.md), following [IMPLEMENTATION.md](docs/IMPLEMENTATION.md). Execute numbered milestones sequentially through implementation, independent review, repair, and acceptance with evidence.
|
- For milestone execution, read and maintain [PLANS.md](PLANS.md), following [IMPLEMENTATION.md](docs/IMPLEMENTATION.md) and [execution record rules](docs/EXECUTION_RECORDS.md). Execute numbered milestones sequentially through implementation, independent review, repair, and acceptance with evidence.
|
||||||
- Read the relevant ADRs: [explicit restart](docs/adr/0001-explicit-restart.md), [client secrets](docs/adr/0002-client-secret-storage.md), [server configuration authority](docs/adr/0003-server-config-authority.md).
|
- Read the relevant ADRs: [explicit restart](docs/adr/0001-explicit-restart.md), [client secrets](docs/adr/0002-client-secret-storage.md), [server configuration authority](docs/adr/0003-server-config-authority.md).
|
||||||
- Read task-specific supporting documents below before editing that area. Their requirements remain applicable; moving them out of this file does not make them optional.
|
- Read task-specific supporting documents below before editing that area. Their requirements remain applicable; moving them out of this file does not make them optional.
|
||||||
- Preserve unrelated user changes. Resolve specific requirement conflicts before implementing affected behavior; continue independent work. Do not reopen settled decisions or seek approval for routine implementation choices.
|
- Preserve unrelated user changes. Resolve specific requirement conflicts before implementing affected behavior; continue independent work. Do not reopen settled decisions or seek approval for routine implementation choices.
|
||||||
@@ -47,7 +47,7 @@ Use the pinned Rust toolchain and environment checks in [ENVIRONMENT.md](docs/EN
|
|||||||
|
|
||||||
Coordinator: **GPT-6-Sol / medium**. Implementation, independent review, repairs, and optional exploration: **GPT-6-Luna / high**, using the configured `implementer`, `reviewer`, `repairer`, and `explorer` roles. Configure actual models through the harness; do not impersonate or silently substitute them.
|
Coordinator: **GPT-6-Sol / medium**. Implementation, independent review, repairs, and optional exploration: **GPT-6-Luna / high**, using the configured `implementer`, `reviewer`, `repairer`, and `explorer` roles. Configure actual models through the harness; do not impersonate or silently substitute them.
|
||||||
|
|
||||||
The coordinator owns task boundaries, PLANS.md updates, and acceptance. Default to one writer; parallel writes require disjoint ownership. Use a separate reviewer from the implementer, repair actionable findings, and verify integrated results before advancing. Do not create Git commits, publish, or deploy merely because a milestone is accepted.
|
The coordinator owns task boundaries, PLANS.md updates, and acceptance. Keep PLANS.md as a current snapshot; put diagnostic attempts in `docs/diagnostics/`, detailed evidence in `docs/evidence/`, and superseded records in `docs/history/`, following [EXECUTION_RECORDS.md](docs/EXECUTION_RECORDS.md). Update statuses in place and keep the register, milestone outcome, and resume point consistent. Default to one writer; parallel writes require disjoint ownership. Use a separate reviewer from the implementer, repair actionable findings, and verify integrated results before advancing. Do not create Git commits, publish, or deploy merely because a milestone is accepted.
|
||||||
|
|
||||||
Build/test without root by default. Never read production private keys, modify production configuration, restart host services, or use the live VPN for testing. Alpine Testcontainers suites are opt-in and isolated; actual systemd/D-Bus acceptance requires a disposable VM. Unavailable prerequisites mean incomplete validation, not success.
|
Build/test without root by default. Never read production private keys, modify production configuration, restart host services, or use the live VPN for testing. Alpine Testcontainers suites are opt-in and isolated; actual systemd/D-Bus acceptance requires a disposable VM. Unavailable prerequisites mean incomplete validation, not success.
|
||||||
|
|
||||||
|
|||||||
Generated
+1672
-41
File diff suppressed because it is too large
Load Diff
@@ -31,3 +31,6 @@ toml = "=1.1.8"
|
|||||||
x25519-dalek = { version = "=2.0.1", default-features = false, features = ["static_secrets", "zeroize"] }
|
x25519-dalek = { version = "=2.0.1", default-features = false, features = ["static_secrets", "zeroize"] }
|
||||||
zeroize = "=1.9.0"
|
zeroize = "=1.9.0"
|
||||||
zbus = { version = "=5.19.0", default-features = false, features = ["async-io"] }
|
zbus = { version = "=5.19.0", default-features = false, features = ["async-io"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
testcontainers = { version = "=0.28.0", default-features = false, features = ["blocking"] }
|
||||||
|
|||||||
@@ -94,13 +94,13 @@ remaining limitations, next concrete action.>
|
|||||||
<Coordinator acceptance decision and timestamp, or exact blocker.>
|
<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.
|
Use ISO 8601 timestamps with offsets for progress. Keep evidence concise and reproducible, following [execution records](docs/EXECUTION_RECORDS.md); diagnostic attempts belong outside this plan. Use 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
|
## 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.
|
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.
|
Container suites are opt-in and use pinned/cached Alpine images, fresh server/client containers, synthetic keys, isolated networking, and scoped `NET_ADMIN`. The real runtime adapter executes inside the server container. Missing prerequisites required by a suite are incomplete validation, not success. Milestone 8 validates IPv4 networking only by user direction; IPv6 capability and traffic remain unverified while the production dual-stack requirement remains in force. 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.
|
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.
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ Each row is a delivery milestone, not a completion claim. Expand its record befo
|
|||||||
| 5 | Runtime observation and systemd restart adapter | 4 | accepted |
|
| 5 | Runtime observation and systemd restart adapter | 4 | accepted |
|
||||||
| 6 | Client configuration delivery and reissue lifecycle | 5 | accepted |
|
| 6 | Client configuration delivery and reissue lifecycle | 5 | accepted |
|
||||||
| 7 | Complete terminal management workflow | 6 | accepted |
|
| 7 | Complete terminal management workflow | 6 | accepted |
|
||||||
| 8 | Alpine Testcontainers integration | 7; suitable disposable Docker environment | pending |
|
| 8 | Alpine Testcontainers integration | 7; suitable disposable Docker environment | accepted |
|
||||||
| 9 | Real systemd acceptance and release readiness | 8; disposable systemd VM | pending |
|
| 9 | Real systemd acceptance and release readiness | 8; disposable systemd VM | pending |
|
||||||
|
|
||||||
### Milestone 1 — Rust foundation, settings, and domain states
|
### Milestone 1 — Rust foundation, settings, and domain states
|
||||||
@@ -2623,9 +2623,361 @@ all planned coordinator gates. M8/M9 retain real WireGuard/systemd release gates
|
|||||||
|
|
||||||
### Milestone 8 — Alpine Testcontainers integration
|
### 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.
|
Status: accepted (2026-10-25T20:43:00+03:00; M8.1–M8.7 complete, independent
|
||||||
|
review closed, scoped IPv4 Alpine gates passed).
|
||||||
|
Prerequisites: milestones 1–7 accepted; pinned Rust toolchain; an explicitly
|
||||||
|
selected disposable Docker-compatible Linux environment with kernel WireGuard,
|
||||||
|
scoped NET_ADMIN and IPv4 support; an Alpine-compatible test executable.
|
||||||
|
Missing prerequisites are blockers/incomplete evidence, never passing tests.
|
||||||
|
|
||||||
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.
|
#### Outcome and scope
|
||||||
|
|
||||||
|
Add an opt-in Rust Testcontainers suite using small Alpine server/client images.
|
||||||
|
Exercise the actual application, encrypted vault/persistence/delivery APIs, and
|
||||||
|
M5's real rustix Generic Netlink reader inside the server container against real
|
||||||
|
kernel WireGuard interfaces. Establish real tunnels, generate IPv4 traffic,
|
||||||
|
and prove that saved changes remain separate from runtime application.
|
||||||
|
The user explicitly excluded IPv6 capability and traffic tests on 2026-10-22.
|
||||||
|
Production dual-stack behavior in SPEC.md is unchanged; real IPv6 behavior is
|
||||||
|
therefore unverified by this milestone.
|
||||||
|
|
||||||
|
Read SPEC.md Operations and states, Connection delivery, Backups and restoration,
|
||||||
|
Client screen and statistics, and acceptance criteria; ADRs 0001–0003; TESTING.md,
|
||||||
|
ENVIRONMENT.md, RUNTIME.md, DELIVERY.md, VAULT_FORMAT.md, PERSISTENCE.md,
|
||||||
|
BACKUP_FORMAT.md, DEPENDENCIES.md and current application/worker code.
|
||||||
|
|
||||||
|
This is real-WireGuard integration, not real-systemd acceptance. Alpine fixtures
|
||||||
|
have no systemd; a test-only apply operation may configure the interface explicitly.
|
||||||
|
Actual zbus service job behavior, production restart and VM acceptance remain M9.
|
||||||
|
M7's synthetic TUI/PTY evidence remains separate; M8 does not require live terminal
|
||||||
|
screens, phone QR decoding, public Internet access, external DNS, production
|
||||||
|
configuration, new product commands, or a production apply mode.
|
||||||
|
|
||||||
|
#### Existing code and integration constraints
|
||||||
|
|
||||||
|
- M1–M7 are accepted. The normal executable now starts the management TUI; do not
|
||||||
|
invoke it with host/production settings as a shortcut for a test driver.
|
||||||
|
- Application has open-from-store, setup/import, creation, Commit, delivery/reissue,
|
||||||
|
deletion, backup/list/restore and confirmed restart flows. Use these real APIs,
|
||||||
|
not a parallel shell-only client manager that would leave application behavior
|
||||||
|
untested. Public-key identity and source preservation remain authoritative.
|
||||||
|
- Real observation is `runtime::transport::observe_interface`, currently crate-
|
||||||
|
private, with the validated decoder and bounded rustix transport. Do not use
|
||||||
|
only `wg show` output or a fake observation as evidence that this reader works.
|
||||||
|
- `Application::restart` always invokes the actual zbus path, while injected
|
||||||
|
restart functions are private. Plan an in-crate test seam so a test-only fixture
|
||||||
|
service operation can apply peers and the real observation/persistence path can
|
||||||
|
verify them. Preserve backup ordering and per-attempt authority from M5.
|
||||||
|
- Prefer cfg(test) in-container test scenarios/driver code, compiled for musl and
|
||||||
|
selected explicitly, to reach private adapters without exposing an unauthenticated
|
||||||
|
public apply API. If another arrangement is needed, document its compile-time
|
||||||
|
isolation before implementation. No environment variable may switch production
|
||||||
|
restart to a fake service or bypass password/backup/root checks.
|
||||||
|
- The TUI ApplicationWorker already has synthetic workflow tests. An optional
|
||||||
|
in-container worker scenario may test its real statistics reader, but network
|
||||||
|
acceptance must not depend on automating every terminal key sequence again.
|
||||||
|
- No Testcontainers crate, container image recipe or integration driver is present.
|
||||||
|
Add approved `testcontainers` as a dev-dependency with compatible pinned version
|
||||||
|
and minimal features. Prefer its supported synchronous facade if it avoids a
|
||||||
|
direct executor dependency. Other direct helpers, Docker client crates or
|
||||||
|
Tokio still require prior agreement; transitive dependencies are not new direct
|
||||||
|
approvals. Use Cargo.lock and inspect official APIs for the selected version.
|
||||||
|
- Native tools use system Rust 1.97.1; musl readiness was previously unverified.
|
||||||
|
Recheck rather than assuming rust-toolchain.toml installed targets or a linker.
|
||||||
|
|
||||||
|
#### Context and implementation steps
|
||||||
|
|
||||||
|
Use GPT-6-Sol/medium coordination and GPT-6-Luna/high implementation, fresh review
|
||||||
|
and repairs. Execute bounded tasks sequentially with one writer and explicit file
|
||||||
|
ownership. The coordinator alone updates milestone acceptance and evidence.
|
||||||
|
|
||||||
|
| Task | Owner | Files and deliverable |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| M8.1 | implementer | `docs/TESTING.md`/ENVIRONMENT additions and test support: exact opt-in architecture, prerequisites, image/build and driver contracts |
|
||||||
|
| M8.2 | implementer | `tests/containers/alpine/Dockerfile`, fixture-only setup scripts, musl build recipe: small reproducible cached image and executable compatibility checks |
|
||||||
|
| M8.3 | implementer | `tests/testcontainers_wireguard.rs`, `tests/support/` modules and Cargo files: host orchestration, private network/container ownership, prerequisite checks, bounded execution and cleanup |
|
||||||
|
| M8.4 | implementer | in-crate cfg(test) scenarios/private adapters, test driver coordination: real netlink reads, fixture apply and application state verification inside server |
|
||||||
|
| M8.5 | implementer | container scenarios and TESTING/README documentation: required lifecycle, restore, delivery/import/failure assertions and executed evidence |
|
||||||
|
| M8.6 | reviewer, repairer | Independent isolation, real-adapter coverage and negative-test review; bounded fixes and real-suite re-review |
|
||||||
|
| M8.7 | coordinator | Default and explicit container gates, redacted results, prerequisites/limitations, acceptance and M9 handoff |
|
||||||
|
|
||||||
|
M8.1 defines two execution locations: host harness uses Testcontainers to manage
|
||||||
|
containers; server-side code uses the real application and kernel reader in that
|
||||||
|
container's namespaces. Document how driver sessions persist across staged create,
|
||||||
|
Commit, first delivery and apply phases, and how the client is configured from the
|
||||||
|
actual delivered file. Do not lose the creation session between Commit and first
|
||||||
|
issue or reconstruct password-free authority on reopen.
|
||||||
|
|
||||||
|
Choose a named ignored integration target/scenarios or another explicit opt-in
|
||||||
|
mechanism that default cargo test never starts. The host suite must also opt into
|
||||||
|
all in-container scenario processes. Ensure --ignored does not select unrelated
|
||||||
|
privileged tests. Fail an explicitly requested suite nonzero on missing required
|
||||||
|
prerequisites; never return early with a successful test result labelled skipped.
|
||||||
|
|
||||||
|
M8.2 pins an Alpine base release/digest and records the resolved image identity
|
||||||
|
and package versions. Install only wireguard-tools, iproute2 and minimal fixture
|
||||||
|
requirements. Use BusyBox capabilities when sufficient; justify any extra package
|
||||||
|
in the test recipe. Runtime images contain no Cargo registry/compiler or systemd.
|
||||||
|
Build/cache image layers and compatible driver artifacts before repeated tests;
|
||||||
|
use fresh mutable containers/volumes for each scenario, not reusable dirty servers.
|
||||||
|
Keep build context small with explicit copies/ignore rules, no .git, host Backup,
|
||||||
|
credentials or private configuration. Never bake generated keys into image layers.
|
||||||
|
|
||||||
|
Build server/client driver code for x86_64-unknown-linux-musl or in a pinned
|
||||||
|
Alpine builder matching the toolchain. Inspect dynamic requirements and actually
|
||||||
|
execute a harmless readiness command inside Alpine. Do not rely on a glibc host
|
||||||
|
binary, target name or successful host compilation as runtime compatibility proof.
|
||||||
|
Only build required test targets; avoid pulling compiler/debug layers into the
|
||||||
|
small runtime fixture. No global package installation or toolchain update is
|
||||||
|
implicitly authorized by a missing cross-build prerequisite.
|
||||||
|
|
||||||
|
M8.3 checks Docker API access and explicit disposable-environment selection.
|
||||||
|
Do not use a configured remote daemon without establishing that it is the selected
|
||||||
|
non-production test environment. Probe WireGuard interface creation inside
|
||||||
|
a disposable probe container, never by creating host interfaces or loading modules.
|
||||||
|
Container tools alone do not supply the host kernel module. Refuse to repair
|
||||||
|
prerequisites via SYS_MODULE, privileged mode or host sysctl changes automatically.
|
||||||
|
|
||||||
|
Create a dedicated uniquely named IPv4 network with a test subnet that does
|
||||||
|
not overlap the tunnel ranges. Run server/client as container root with scoped
|
||||||
|
NET_ADMIN in their own network/PID namespaces; no host networking, namespace joins
|
||||||
|
to host, host D-Bus, host /etc/wireguard, Docker socket mount, or production binds.
|
||||||
|
No host UDP publication is needed for container-to-container endpoints. Use
|
||||||
|
container-internal reachable endpoint/port values, not localhost mapped ports.
|
||||||
|
All settings/server/vault/output paths are private fixture directories created
|
||||||
|
with the application's required permissions.
|
||||||
|
|
||||||
|
Use Testcontainers copy/exec APIs or an explicitly safe bounded transport for
|
||||||
|
fixture data. Secret-bearing configs/passwords never enter command arguments,
|
||||||
|
image metadata, public environment variables or raw stdout/stderr logs. Any
|
||||||
|
necessary copied plaintext exists only in private temporary/container storage.
|
||||||
|
Generate keys per scenario. Reports carry public keys, phase/status and redacted
|
||||||
|
categories, not full configurations, QR matrices or `wg show all dump` output.
|
||||||
|
|
||||||
|
Bound image/start/driver/readiness/traffic/teardown operations. Poll explicit
|
||||||
|
conditions with monotonic deadlines, not fixed sleeps. Use unique ownership and
|
||||||
|
RAII/explicit cleanup for containers, networks and volumes on failures as well as
|
||||||
|
success; cleanup must not mask the original failure or delete unrelated resources.
|
||||||
|
Do not default to retain-failed-containers with secrets. Report leftover owned
|
||||||
|
resources if daemon failure prevents cleanup.
|
||||||
|
|
||||||
|
M8.4 keeps fixture `ip`/`wg` calls solely in setup or test-only apply code. It may
|
||||||
|
use wg-quick strip or equivalent explicit fixture setup, but must not execute
|
||||||
|
arbitrary imported hooks. Handle optional DNS/MTU fields in delivered configs via
|
||||||
|
a documented fixture setup, without mutating the delivered payload just to pass.
|
||||||
|
Avoid an unneeded resolvconf dependency by configuring networking explicitly when
|
||||||
|
appropriate. No subprocess apply path is added to production.
|
||||||
|
|
||||||
|
Configure endpoint reachability outside the tunnel before full-tunnel policy so
|
||||||
|
an encrypted UDP endpoint is not recursively routed into its own VPN. Set full
|
||||||
|
IPv4 routes within the client's isolated namespace only. Exercise traffic
|
||||||
|
to server tunnel addresses; an external Internet gateway/DNS is not required and
|
||||||
|
successful tunnel reachability does not claim public Internet egress works.
|
||||||
|
Ensure test success cannot come from the Docker underlay: target tunnel-only
|
||||||
|
addresses, use an explicit interface/route assertion, and verify public counters
|
||||||
|
through the actual rustix reader. Test-only service apply reports must be labelled
|
||||||
|
fixture results, never actual systemd job evidence.
|
||||||
|
|
||||||
|
If fixture restart orchestration verifies and releases application reservations,
|
||||||
|
use the existing private adapter seam with a real backup, fixture apply, actual
|
||||||
|
netlink read/comparison and journaled verification. Do not simply call
|
||||||
|
mark_applied or clear reservations to satisfy assertions. A direct fixture apply
|
||||||
|
outside that orchestration proves only kernel state change; it cannot by itself
|
||||||
|
prove the production successful-restart state transition.
|
||||||
|
|
||||||
|
M8.5 implements these required scenarios, splitting tests where isolation helps:
|
||||||
|
|
||||||
|
1. **Baseline and reader interoperability.** Bring up a server plus an existing
|
||||||
|
client with synthetic configuration, establish real IPv4 tunnel traffic,
|
||||||
|
and observe the configured interface's complete peer set, routes, nonzero fresh
|
||||||
|
handshake and increasing counters through the actual Rust reader. Capture
|
||||||
|
before/after counters, not exact packet-byte totals. Empty/not-yet-handshaken
|
||||||
|
observations must not be misreported as a reader failure or online heuristic.
|
||||||
|
2. **Create, Commit, deliver, then apply.** Use Application to prepare a new client,
|
||||||
|
Commit and save the actual first-delivery file (QR optional). Assert saved
|
||||||
|
server/vault change, mandatory pre-Commit archive, unchanged runtime peers and
|
||||||
|
explicit activation warning. Configure a fresh client from delivered bytes;
|
||||||
|
prove it cannot reach tunnel-only targets before apply. Apply through the
|
||||||
|
test-only boundary, then require real handshake and IPv4 traffic.
|
||||||
|
3. **Saved deletion versus live access.** Delete and Commit an active client;
|
||||||
|
assert current vault record removal, existing backup retention, and unchanged
|
||||||
|
runtime peer/access before apply. Reopen saved state to verify the IP remains
|
||||||
|
reserved. After fixture restart plus real verification/persistence, assert
|
||||||
|
peer removal, failed fresh tunnel traffic and address release. Use a finite
|
||||||
|
negative-check window and establish a working baseline so unrelated setup
|
||||||
|
failure cannot masquerade as successful revocation.
|
||||||
|
4. **Backup/restore separation.** Save a known client set, mutate/Commit, preview
|
||||||
|
and restore the archive without providing a vault password. Assert restored
|
||||||
|
settings/server/vault/state and precautionary backup, plus unchanged kernel
|
||||||
|
state until explicit fixture apply. Then verify the restored peer set and
|
||||||
|
renewed tunnel traffic. Restoration alone never releases pending addresses.
|
||||||
|
5. **Import, reissue and file safety.** Import a valid generated existing client
|
||||||
|
plus invalid/conflicting candidates from private source files. Assert original
|
||||||
|
bytes unchanged, anomalies visible and conflicts blocked. Reopen and reissue
|
||||||
|
a matched client through password-authorized output; verify wrong password
|
||||||
|
fails and a valid delivered file can configure a real tunnel. Preserve first-
|
||||||
|
issue/password boundaries and do not add plaintext source fallback.
|
||||||
|
6. **Failure prevents application.** Produce a deterministic backup failure in the
|
||||||
|
container (not chmod-only when root can bypass it) and assert Commit/fixture
|
||||||
|
restart does not install/apply prepared changes or release addresses. Cover
|
||||||
|
default reader deadline/missing-interface behavior inside its namespace without
|
||||||
|
changing host state. Keep already exhaustive malformed/archive/failure-unit
|
||||||
|
cases in ordinary tests rather than duplicating them in every costly container.
|
||||||
|
|
||||||
|
For all negative reachability assertions, stop/restart client traffic as needed
|
||||||
|
and use new requests; stale timestamps/counters or buffered packets cannot prove
|
||||||
|
continued/revoked access. Use a fixture endpoint/traffic generator with its own
|
||||||
|
bounded error classification. Record that IPv6 traffic is intentionally untested.
|
||||||
|
|
||||||
|
#### Acceptance and validation
|
||||||
|
|
||||||
|
- [x] M8-A1: documented opt-in commands run an actual Testcontainers suite with
|
||||||
|
pinned/cached Alpine runtime images and compatible in-container driver; default
|
||||||
|
tests/help/version do not start Docker, require root or query host interfaces.
|
||||||
|
- [x] M8-A2: explicit prerequisite checks reject unavailable daemon/kernel/capability
|
||||||
|
or incompatible artifacts as incomplete/failure; no production or automatic
|
||||||
|
privileged/module/sysctl workaround is used.
|
||||||
|
- [x] M8-A3: isolated real peers establish IPv4 traffic, and the actual
|
||||||
|
rustix reader reports complete public peer/routes, fresh handshake and traffic
|
||||||
|
increments without relying solely on wg text or fake decoded observations.
|
||||||
|
- [x] M8-A4: new client creation/Commit/file delivery use real Application and vault
|
||||||
|
APIs; the delivered configuration works only after explicit fixture application,
|
||||||
|
and Commit itself never changes runtime state.
|
||||||
|
- [x] M8-A5: saved deletion removes only current secret, keeps archives and retains
|
||||||
|
reservation/live peer until explicit apply; actual observation and journaled
|
||||||
|
verification precede reservation release. Fresh traffic is revoked afterward.
|
||||||
|
- [x] M8-A6: password-free restore replaces the saved set but does not apply it;
|
||||||
|
fixture apply restores real tunnel behavior. Source files remain unchanged and
|
||||||
|
imported conflict/wrong-password outputs remain blocked.
|
||||||
|
- [x] M8-A7: deterministic mandatory-backup failure prevents protected write/apply;
|
||||||
|
negative traffic and missing-interface results cannot pass due to unrelated
|
||||||
|
broken setup or a silent empty/partial observation.
|
||||||
|
- [x] M8-A8: fixture adapter cannot be selected in production or bypass root/password/
|
||||||
|
backup controls; no public alternate restart API or runtime environment bypass
|
||||||
|
is introduced. Alpine results explicitly exclude actual zbus/systemd coverage.
|
||||||
|
- [x] M8-A9: secret-free diagnostics, private copied files, limits/deadlines and
|
||||||
|
cleanup work on both success and failure; only test-owned resources are removed.
|
||||||
|
- [x] M8-A10: integrated default and real-container gates pass; a clean repeated run
|
||||||
|
reuses image layers while starting fresh mutable scenarios and produces the same
|
||||||
|
behavioral outcomes. Independent review closes actionable isolation/coverage issues.
|
||||||
|
|
||||||
|
Executed integrated host and selected-daemon commands, from repository root;
|
||||||
|
the exact Docker environment, image/driver commands, counts and durations are
|
||||||
|
in [the M8.7 evidence](docs/evidence/milestone-8.md):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo fmt --all -- --check
|
||||||
|
cargo clippy --locked --all-targets -- -D warnings
|
||||||
|
cargo test --locked
|
||||||
|
cargo build --locked --release
|
||||||
|
cargo test --locked --test testcontainers_wireguard -- --ignored --test-threads=1
|
||||||
|
./target/release/wireguard-admin --help
|
||||||
|
./target/release/wireguard-admin --version
|
||||||
|
git diff --check
|
||||||
|
```
|
||||||
|
|
||||||
|
The exact pinned image-build and in-container driver-build commands,
|
||||||
|
prerequisites, and output artifact path are in [TESTING.md](docs/TESTING.md).
|
||||||
|
Both were executed; the test-only musl driver compiled and ran in Alpine.
|
||||||
|
Build setup and cached dependency access are distinct from the locked host
|
||||||
|
test execution. No unapproved direct dependency was added.
|
||||||
|
|
||||||
|
Image identities, kernel/runtime/toolchain versions, target architecture,
|
||||||
|
scenario names, outcomes, durations and counts are recorded in the evidence
|
||||||
|
report. All required scenarios executed and passed; the ignored-test output
|
||||||
|
explicitly reports 1/1 passing rather than treating a skipped test as success.
|
||||||
|
Real systemd validation remains M9.
|
||||||
|
|
||||||
|
#### Progress
|
||||||
|
|
||||||
|
Current snapshot: 2026-10-25. This replaces the chronological execution notes;
|
||||||
|
[the historical record](docs/history/milestone-8-before-workflow-cleanup.md)
|
||||||
|
preserves previous attempts and their reported evidence.
|
||||||
|
|
||||||
|
| Task | State | Result / next action |
|
||||||
|
| ------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| M8.1 | complete | Reviewed host/container contract. |
|
||||||
|
| M8.2 | complete | Pinned Alpine recipe, package closure and readiness verified. |
|
||||||
|
| M8.3 | complete | Reviewed IPv4 prerequisite probe passed on the selected daemon. |
|
||||||
|
| M8.4 | complete | Reviewed in-container Application driver and real Rustix smoke passed. |
|
||||||
|
| M8.5 traffic | complete | Copied first-delivery file drives real IPv4 traffic; exact IPv4 routes, handshakes and counter deltas verified; independent findings closed. |
|
||||||
|
| M8.5 deletion | complete | Saved deletion preserves access until explicit fixture Apply; subsequent deleted-client traffic fails and retained-client traffic succeeds; independent finding closed. |
|
||||||
|
| M8.5 restore | complete | Two password-free restores, unchanged runtime before Apply, renewed IPv4 traffic and pending-address release after verified Apply passed on the selected daemon. Independent code review found no implementation defect; the plan-status finding closed on re-review. |
|
||||||
|
| M8.5 import/reissue | complete | M8.5-IMP-01 closed on independent re-review; focused tests and fingerprinted pinned musl build passed. Selected-daemon suite passed 1/1 in 82.06s with cleanup, including reissued-file IPv4 traffic. |
|
||||||
|
| M8.5 backup failure | complete | M8.5-BF-R1 closed on independent re-review. Typed backup errors, unchanged saved/runtime state, no fixture apply, retained reservation and reader errors passed on a known-source selected-daemon run (1/1, 81.45s). |
|
||||||
|
| M8.6 | complete | R1 documentation, R2 IPv6 plan-gate and R3 stale milestone summary findings each closed on independent re-review. No code defect was found. |
|
||||||
|
| M8.7 | complete | Cached image/readiness/packages, pinned musl rebuild, host gates, two fresh selected-daemon full runs and the documented unfiltered opt-in command passed on stable source inputs. Final independent review closed all findings; coordinator accepted M8-A1–A10 for the scoped IPv4 Alpine work. |
|
||||||
|
|
||||||
|
#### Review and repair
|
||||||
|
|
||||||
|
| Finding / work item | State | Disposition / next evidence |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| M8.1-R1; M8.2-R1/R2 | closed | Independent re-review closed documentation/image findings. |
|
||||||
|
| M8.3-R1–R7 | closed | Timeout/transport/error/name fixes reviewed; revised IPv4 probe passed. |
|
||||||
|
| M8.4-R1–R4 | closed | Driver protocol, path confinement, host transport and cleanup findings independently closed; real smoke passed. |
|
||||||
|
| M8.5-R1/R2 | closed | Delivered bytes consumed in client namespace; exact IPv4 route assertions; independent re-review closed both. |
|
||||||
|
| M8.5-R3 | closed | Negative ping requires completed transmitted/zero-received evidence; Docker execution errors cannot pass; independent re-review closed it. |
|
||||||
|
| M8.5 restore phase failure | closed | Fixture expected only the removed client's IPv4 reservation; Application correctly retained both saved addresses. Exact-set expectation now derives all removed-client addresses; focused regression and selected-daemon pass. [Diagnostic record](docs/diagnostics/milestone-8.md). |
|
||||||
|
| M8.5-RESTORE-DOC-1 | closed | Independent reviewer found stale restore status/handoff; coordinator updated the snapshot, and independent re-review closed it. No code defect found. |
|
||||||
|
| M8.5-IMP-01 | closed | P2: the Alpine driver had omitted invalid-file byte preservation. Repair checks all three sources before claiming unchanged originals; focused regression and fingerprinted selected-daemon rerun passed, and independent re-review closed it. |
|
||||||
|
| M8.5 failure phase | closed | Fixture misread `delete_client`'s bool as peer deletion; it actually reports vault-record removal and the legacy baseline has no record. Repair checks draft peer absence; focused regression and known-source selected-daemon run passed; independent review found no further issue with this check. |
|
||||||
|
| M8.5-BF-R1 | closed | P2: Commit-failure assertion formerly accepted any error. Repair requires `Err(AppError::Backup(_))`; focused regression and known-source selected-daemon run passed, and independent re-review closed it. |
|
||||||
|
| M8.6-R1 | closed | P2: TESTING.md and evidence report had stale M8.5 pending claims. Repairer recorded completed first-delivery transfer and fingerprinted backup-failure evidence; independent re-review closed it. |
|
||||||
|
| M8.6-R2 | closed | P2: General plan gate required IPv6 prerequisites despite M8's explicit IPv4-only scope. Coordinator scoped prerequisite language to the suite and retained production dual-stack requirement as unverified; independent re-review closed it. |
|
||||||
|
| M8.6-R3 | closed | P2: Milestone status summary still reported active M8.5 restore repair. Coordinator updated it to M8.1–M8.5 complete with M8.6/M8.7 open; independent re-review closed it. |
|
||||||
|
| M8.7-R1 | closed | P2: Reviewer saw a prior plan snapshot while final evidence was being entered. Task/handoff rows and outcome now record passing repeated runs and R3 closure; independent re-review closed it. |
|
||||||
|
| M8.7-R2 | closed | P3: Validation passage still described implemented and executed commands as planned, and snapshot date was stale. Coordinator corrected the passage and date; independent re-review closed it. |
|
||||||
|
|
||||||
|
Detailed closed-finding rationale and superseded attempts are in the historical
|
||||||
|
record. New diagnostic attempts belong in `docs/diagnostics/`, not this table.
|
||||||
|
|
||||||
|
#### Evidence and handoff
|
||||||
|
|
||||||
|
Previously reported evidence, carried forward without rerunning during this
|
||||||
|
2026-10-24 documentation cleanup:
|
||||||
|
|
||||||
|
| Scope | Result and limit |
|
||||||
|
| --- | --- |
|
||||||
|
| IPv4 prerequisite | Coordinator probe passed 1/1 (1.46s); isolated resources cleaned. |
|
||||||
|
| M8.4 Application smoke | Coordinator musl rebuild and selected-daemon smoke passed 1/1 (4.05s); named resources absent afterward. |
|
||||||
|
| M8.5 delivered-file traffic | Repaired real run passed 1/1 (14.23s); host support tests passed 21 with privileged test ignored; independent re-review closed R1/R2. |
|
||||||
|
| M8.5 deletion | Repaired coordinator real run passed 1/1 (42.96s); host support tests passed 23 with privileged test ignored; cleanup verified; independent re-review closed R3. |
|
||||||
|
| M8.5 restore | Worker reported fmt, focused/full locked tests, all-target Clippy, release build and diff check passing before the final reservation-helper edit; focused helper regression and pinned musl rebuild passed afterward. Coordinator exact selected-daemon run passed 1/1 (72.66s), with named resources absent. Independent review found no code issue; later integrated gates are recorded in the M8.7 row. |
|
||||||
|
| M8.5 import/reissue | Worker reported fmt, Clippy and focused import tests after its final assertion edit; full locked tests and release build passed before it. After M8.5-IMP-01 repair, focused regressions and Clippy passed. Coordinator pinned musl build and selected-daemon run passed 1/1 (82.06s), with stable source fingerprint and named resources absent. [Reproducible evidence](docs/evidence/milestone-8.md). |
|
||||||
|
| M8.5 backup failure | Worker reported fmt, Clippy, full locked tests/release before the final typed-error repair; focused regression, fmt and Clippy passed afterward. Coordinator pinned musl build and selected-daemon run passed 1/1 (81.45s), source-input SHA256 `b28dc6e4cb92aa6edf384d966ba5952a8d6d0efe4c22d6a804b67fb2c0297da2` stable before build/after run, driver SHA256 `ad45dc3f6b0987b075ac522e171c82c6651124c3d01c65c14df90595fe552815`, named resources absent. Independent re-review closed BF-R1. |
|
||||||
|
| M8.7 integrated gates | On the same source fingerprint, `cargo fmt --all -- --check`, `cargo clippy --locked --all-targets -- -D warnings`, `cargo test --locked`, `cargo build --locked --release`, release help/version, and `git diff --check` exited 0. Cached Alpine image ID `sha256:f41cd6b848d8280d4d86bcaceea962e1b3e5a73e9496c6f9d7929470d0e874d5` passed readiness; 33 constrained packages recorded. Pinned musl rebuild produced the same driver SHA256. Exact selected-daemon scenario passed 1/1 twice (80.77s each); documented unfiltered `--ignored` command passed 1/1 (81.87s). Named containers/networks absent after each. [Reproducible evidence](docs/evidence/milestone-8.md); prerequisite failures in [diagnostics](docs/diagnostics/milestone-8.md). Final independent review closed M8.7-R1/R2 and found M8-A1–A10 supported. |
|
||||||
|
|
||||||
|
Exact earlier commands, environment/build identities and intermediate results
|
||||||
|
remain in the historical record. For new evidence use the report format in
|
||||||
|
[execution records](docs/EXECUTION_RECORDS.md), including the tested revision or
|
||||||
|
working-tree identity. Earlier runs lack a complete source fingerprint; do not
|
||||||
|
infer that they validate subsequent edits. Final gates must run on the integrated
|
||||||
|
revision. Do not rerun unrelated gates merely to maintain this document.
|
||||||
|
|
||||||
|
Next action: Milestone 8 is accepted. Stop here; Milestone 9 remains pending and
|
||||||
|
requires a disposable systemd VM before its real D-Bus acceptance work.
|
||||||
|
|
||||||
|
#### Decisions and discoveries
|
||||||
|
|
||||||
|
- The user excluded IPv6 capability and traffic testing on 2026-10-22;
|
||||||
|
production dual-stack requirements are unchanged. Real IPv6 remains unverified.
|
||||||
|
- Use the real bounded Rustix reader and test-only fixture Apply. Alpine cannot
|
||||||
|
validate the private zbus/systemd adapter; real systemd acceptance remains M9.
|
||||||
|
- Build the musl test driver in the pinned Alpine builder; the host lacks the
|
||||||
|
musl prerequisites. Cached image/build layers may be reused; secret-bearing
|
||||||
|
fixture state must be fresh. Build identities are preserved in the history.
|
||||||
|
- Full fixture `wg setconf` resets learned peer state. Traffic assertions reconnect
|
||||||
|
retained clients and require fresh observations; peer presence alone is not
|
||||||
|
continuity evidence.
|
||||||
|
|
||||||
|
#### Outcome
|
||||||
|
|
||||||
|
M8 accepted for its user-scoped IPv4 Alpine Testcontainers work after independent
|
||||||
|
review closed all actionable findings and the host and repeated real-container
|
||||||
|
gates passed on one source fingerprint. M8-A1–A10 are satisfied within that
|
||||||
|
scope. IPv6 capability and traffic were excluded by user direction; real
|
||||||
|
systemd/D-Bus behavior remains unverified and belongs to M9.
|
||||||
|
Milestone 9 remains pending.
|
||||||
|
|
||||||
### Milestone 9 — Real systemd acceptance and release readiness
|
### Milestone 9 — Real systemd acceptance and release readiness
|
||||||
|
|
||||||
@@ -2635,7 +2987,7 @@ Acceptance: real D-Bus job handling, separate restart verification, address rele
|
|||||||
|
|
||||||
## Current progress and resume point
|
## Current progress and resume point
|
||||||
|
|
||||||
- Milestones 1–7 are accepted; milestones 8–9 remain pending.
|
- Milestones 1–8 are accepted; milestone 9 remains pending.
|
||||||
- The approved dependency list is in [DEPENDENCIES.md](docs/DEPENDENCIES.md); container testing methodology is in [TESTING.md](docs/TESTING.md).
|
- 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.
|
- 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 2 delivered a lossless in-memory WireGuard configuration editor, versioned metadata, and behavioral preservation tests.
|
||||||
@@ -2646,8 +2998,17 @@ Acceptance: real D-Bus job handling, separate restart verification, address rele
|
|||||||
- M1–M6 integrated gates and independent reviews passed. No Alpine or VM results are claimed.
|
- M1–M6 integrated gates and independent reviews passed. No Alpine or VM results are claimed.
|
||||||
- Milestone 7 delivered the native terminal workflow, safe startup/session projection, bounded single-owner worker, setup/import/draft/Commit/restart/delivery/backup/restore forms, synthetic PTY and workflow tests, and documentation. Independent R2 closed its list-status finding; all planned coordinator gates passed. Real WireGuard and systemd remain M8/M9.
|
- Milestone 7 delivered the native terminal workflow, safe startup/session projection, bounded single-owner worker, setup/import/draft/Commit/restart/delivery/backup/restore forms, synthetic PTY and workflow tests, and documentation. Independent R2 closed its list-status finding; all planned coordinator gates passed. Real WireGuard and systemd remain M8/M9.
|
||||||
|
|
||||||
|
- Milestone 8 delivered reviewed opt-in Alpine Testcontainers coverage for real IPv4 WireGuard peers, application lifecycle, saved/runtime separation, restoration, import/reissue, backup failures and the real Rustix reader. Host gates and three fresh real-container runs passed on one stable source fingerprint; M8-A1–A10 accepted. Real IPv6 behavior is unverified by user-authorized scope.
|
||||||
|
- Next action: stop after M8. Milestone 9 remains pending for disposable-VM systemd/D-Bus acceptance; no production access, commit or deployment is authorized by M8 acceptance.
|
||||||
|
|
||||||
## Plan maintenance
|
## 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.
|
Follow [Execution records and diagnostic workflow](docs/EXECUTION_RECORDS.md).
|
||||||
|
Update the current snapshot in place after a task result, review disposition,
|
||||||
|
blocker/scope change, acceptance, or handoff. Do not append command-by-command
|
||||||
|
attempts or worker transcripts. Keep register, milestone outcome and resume point
|
||||||
|
consistent. Preserve final evidence and scope-change reasons; archive superseded
|
||||||
|
history with a link before removing it. Change the specification only with actual
|
||||||
|
user authorization.
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|||||||
+1
-1
@@ -43,7 +43,7 @@ with permission to create Git commits.
|
|||||||
|
|
||||||
The root instructions define prepare, implement, review, repair, re-review, and
|
The root instructions define prepare, implement, review, repair, re-review, and
|
||||||
acceptance. PLANS.md is the durable record of milestone/task IDs, ownership,
|
acceptance. PLANS.md is the durable record of milestone/task IDs, ownership,
|
||||||
progress, findings, validation, and blockers. The TOML files configure model
|
progress, findings, validation, and blockers. Follow [execution record rules](EXECUTION_RECORDS.md): update current state in place, keep detailed investigations in `docs/diagnostics/`, detailed evidence in `docs/evidence/`, and superseded history in `docs/history/`. Include these boundaries in worker dispatches; reassess repeated failed repairs before another expensive run. The TOML files configure model
|
||||||
selection and agent instructions; they do not implement a deterministic workflow
|
selection and agent instructions; they do not implement a deterministic workflow
|
||||||
engine or automatically start execution.
|
engine or automatically start execution.
|
||||||
|
|
||||||
|
|||||||
+63
-5
@@ -46,15 +46,73 @@ all native compilers/linkers needed by a cross build. Establish the musl linker
|
|||||||
and any C dependency requirements before milestone 8, or build in a dedicated
|
and any C dependency requirements before milestone 8, or build in a dedicated
|
||||||
Alpine/musl builder. Keep build tools out of the small runtime test image.
|
Alpine/musl builder. Keep build tools out of the small runtime test image.
|
||||||
|
|
||||||
Once prerequisites and Cargo scaffolding exist, the planned cross-build check is:
|
The Alpine suite has two separate execution environments:
|
||||||
|
|
||||||
|
- The host runs the Rust Testcontainers harness and must have the pinned Rust
|
||||||
|
toolchain, approved development dependencies, and access to an explicitly
|
||||||
|
selected disposable Docker-compatible Linux daemon. Testcontainers manages
|
||||||
|
only scenario-owned networks/containers. Do not silently use a remote daemon
|
||||||
|
or the default local daemon before it has been selected as disposable.
|
||||||
|
- The daemon's Linux host must already provide kernel WireGuard support and
|
||||||
|
IPv4 networking. Server and client containers share that kernel but receive
|
||||||
|
only scoped `NET_ADMIN` in their own network namespaces. The suite verifies
|
||||||
|
support in disposable probe containers on a private internal IPv4 network. It
|
||||||
|
must not load modules, alter host sysctls, or create host interfaces. Missing
|
||||||
|
kernel, daemon, IPv4, or namespace prerequisites are an incomplete explicit
|
||||||
|
run, not a passing skip. IPv6 capability and traffic are intentionally not
|
||||||
|
tested by M8; production dual-stack behavior remains unchanged.
|
||||||
|
|
||||||
|
The fixture image is pinned to an Alpine release or digest and records the
|
||||||
|
resolved image identity and complete installed package closure. It contains
|
||||||
|
runtime utilities only (`wireguard-tools`, `iproute2`, BusyBox tools actually
|
||||||
|
used, and any justified additions). Driver
|
||||||
|
and fixture inputs are staged per fresh scenario container; keys and configs
|
||||||
|
must never enter the image build context or layers. The test-only server driver
|
||||||
|
must be built for `x86_64-unknown-linux-musl` or inside a pinned Alpine/musl
|
||||||
|
builder matching the repository toolchain, then checked for actual dynamic
|
||||||
|
requirements and executed in the fixture image. A successful cross build alone
|
||||||
|
does not establish Alpine compatibility.
|
||||||
|
|
||||||
|
The current host observation above records that the musl standard library is
|
||||||
|
missing. Do not install it or a linker implicitly. The fixture recipe now lives
|
||||||
|
in `tests/containers/alpine/` and the exact image commands are in
|
||||||
|
[TESTING.md](TESTING.md). The base is pinned to Alpine 3.22.7 for linux/amd64;
|
||||||
|
the selected disposable daemon built and cached the image, and the exact
|
||||||
|
installed closure is recorded in `packages.lock`. Compiler/toolchain layers stay
|
||||||
|
out of this runtime image.
|
||||||
|
|
||||||
|
M8.4 provides an in-crate `#[cfg(test)]` library test driver so that its fixture
|
||||||
|
command and restart seams stay inaccessible to the production executable. It
|
||||||
|
includes an ignored test named `container_driver::alpine_readiness`, selected
|
||||||
|
with libtest's `--ignored --exact` arguments. This harmless test checks only its
|
||||||
|
Linux x86_64 execution environment. Once the musl standard library and a
|
||||||
|
compatible linker are available, build the library test harness from the
|
||||||
|
repository root with:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo build --locked --release --target x86_64-unknown-linux-musl
|
cargo test --locked --lib --no-run --target x86_64-unknown-linux-musl
|
||||||
```
|
```
|
||||||
|
|
||||||
Then run the built executable in Alpine and inspect actual dynamic-library
|
The host cannot run this build directly because its musl target is absent.
|
||||||
requirements. A target name alone is not evidence that the binary runs there.
|
The digest-pinned official Rust 1.97.1 Alpine builder command in
|
||||||
Musl readiness is a milestone 8 prerequisite, not a milestone 1 acceptance gate.
|
[TESTING.md](TESTING.md) built the library test harness on 2026-10-23. `file`
|
||||||
|
identified a static PIE and `readelf -l` showed no interpreter; the ignored
|
||||||
|
readiness selector then passed 1/1 in the pinned Alpine runtime using a
|
||||||
|
read-only bind of the nonsecret binary. The selected-daemon M8.4 Testcontainers
|
||||||
|
smoke later copied the current binary into a fresh server container and passed
|
||||||
|
the test-only Create/Commit/first delivery/fixture Apply sequence with real
|
||||||
|
runtime observation. M8.5 traffic and remaining lifecycle scenarios remain
|
||||||
|
pending. The readiness wrapper
|
||||||
|
invokes the harness as
|
||||||
|
`PATH --ignored --exact container_driver::alpine_readiness --test-threads=1`.
|
||||||
|
Cargo printed the executable under
|
||||||
|
`/tmp/wga-m8-build-target/x86_64-unknown-linux-musl/debug/deps/wireguard_admin-<hash>`
|
||||||
|
for the builder command. Discover the current hash from each build; the driver
|
||||||
|
must be recopied after code changes. A target name or successful compilation
|
||||||
|
alone is not Alpine runtime evidence. Shell-tool readiness, Rust harness
|
||||||
|
readiness and the M8.4 Application smoke passed on the selected daemon. No
|
||||||
|
client tunnel traffic or systemd job has been tested. Musl readiness is a
|
||||||
|
milestone 8 prerequisite, not a milestone 1 acceptance gate.
|
||||||
|
|
||||||
## Observed preparation state
|
## Observed preparation state
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
# Execution records and diagnostic workflow
|
||||||
|
|
||||||
|
This document governs development records, not application logging. It supplements
|
||||||
|
[the implementation order](IMPLEMENTATION.md) and [agent instructions](../AGENTS.md).
|
||||||
|
Product requirements, independent review and acceptance gates are unchanged.
|
||||||
|
|
||||||
|
## Record boundaries
|
||||||
|
|
||||||
|
| Record | Purpose | Contents |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| [PLANS.md](../PLANS.md) | Current execution contract and resume point | Scope, acceptance checklist, task state/owner, open findings, concise closed-finding disposition, latest applicable evidence, limitations and next action. |
|
||||||
|
| `docs/diagnostics/milestone-N.md` | Investigation notebook, created only when needed | Failed attempts, hypotheses, bounded diagnostics, observations, cause and repair verification. |
|
||||||
|
| `docs/evidence/milestone-N.md` | Reproducible evidence report, created when evidence no longer fits a concise plan table | Exact commands, source/build identity, environment, outcomes, review disposition and acceptance mapping. |
|
||||||
|
| `docs/history/` | Preserved superseded records | Historical snapshots with an explicit non-current notice and a link back to the plan. |
|
||||||
|
|
||||||
|
The plan is a current snapshot, not an append-only diary. Do not copy worker
|
||||||
|
reports or full diagnostic sequences into it. Keep the detailed report in one
|
||||||
|
place and link it. Evidence and diagnostic reports never override the plan's
|
||||||
|
scope or the specification. Read only the relevant investigation/report when
|
||||||
|
resuming; reading all historical logs is not a prerequisite for implementation.
|
||||||
|
|
||||||
|
## Updating the plan
|
||||||
|
|
||||||
|
The coordinator updates a task row in place when its result, owner, next action,
|
||||||
|
review disposition or blocker changes, and before handoff. A failed command alone
|
||||||
|
does not need a plan entry unless it changes current status or invalidates evidence.
|
||||||
|
For an active failure keep one concise entry: affected behavior, known facts,
|
||||||
|
owner, next discriminating check and acceptance impact. Replace it as facts change.
|
||||||
|
|
||||||
|
Keep the milestone register, milestone status/outcome and top-level resume point
|
||||||
|
consistent in the same edit. Use the existing lifecycle states; distinguish a
|
||||||
|
repairable defect (`needs_changes`) from an unavailable prerequisite (`blocked`).
|
||||||
|
Task completion does not imply milestone acceptance. Closed findings retain their
|
||||||
|
stable ID, effect, final resolution and review/evidence reference; intermediate
|
||||||
|
repair attempts belong in diagnostics. Superseded passing runs are historical,
|
||||||
|
not additional proof for the current revision.
|
||||||
|
|
||||||
|
Aim for at most one short paragraph per task/finding and one row per evidence
|
||||||
|
scope. This is a readability target, not permission to omit acceptance criteria,
|
||||||
|
required evidence, unresolved findings or safety constraints. If detail exceeds
|
||||||
|
that target, move the detail to the appropriate linked report.
|
||||||
|
|
||||||
|
## Investigating failures
|
||||||
|
|
||||||
|
1. Classify the failure: prerequisite, fixture/transport, application behavior,
|
||||||
|
assertion, or currently unknown. Do not assume the application or fixture is
|
||||||
|
correct before checking.
|
||||||
|
2. Record a concrete hypothesis and the smallest bounded check that distinguishes
|
||||||
|
it from alternatives. Reuse the same task/finding ID across attempts.
|
||||||
|
3. Run that check and record the observation. Retry unchanged commands only for
|
||||||
|
an evidenced transient condition or deliberate repeatability check; record why.
|
||||||
|
4. After two failed repair attempts for the same symptom, reassess the cause and
|
||||||
|
task boundary before another expensive full run. Obtain a focused independent
|
||||||
|
review or reduce to a minimal reproduction when useful. This is an escalation
|
||||||
|
of investigation, not an arbitrary acceptance cutoff or a reason to ask the
|
||||||
|
user to resolve routine implementation choices.
|
||||||
|
5. Repair the cause, run focused checks, then rerun affected integration behavior.
|
||||||
|
Rebuild the container driver when its inputs changed; reuse a compatible pinned
|
||||||
|
builder/cache. Do not run the full Rust gates for every diagnostic edit. Run
|
||||||
|
them at the required completion gates and again if subsequent changes invalidate
|
||||||
|
them, following [TESTING.md](TESTING.md).
|
||||||
|
6. Review the actual repair and affected behavior independently. Consolidate the
|
||||||
|
plan to the final result, remaining uncertainty and next action.
|
||||||
|
|
||||||
|
A diagnostic entry contains: ISO timestamp with offset; task/finding ID; symptom;
|
||||||
|
hypothesis; exact safe command or reproduction reference; observed result; cause
|
||||||
|
if established; next action or closure/evidence reference. Record only useful
|
||||||
|
redacted excerpts. Raw logs, secrets, configuration payloads, private keys and
|
||||||
|
working backups must not be committed. An agent stall or usage limit needs a
|
||||||
|
brief handoff note only when it affects ownership or the next action.
|
||||||
|
|
||||||
|
## Evidence and handoff
|
||||||
|
|
||||||
|
An evidence entry identifies:
|
||||||
|
|
||||||
|
- Task/criterion and run timestamp with offset.
|
||||||
|
- Tested commit plus dirty-state description, or a reproducible fingerprint of
|
||||||
|
relevant tracked/untracked source inputs; identify the built artifact and its
|
||||||
|
source inputs when a separate container binary is used. A commit alone does
|
||||||
|
not identify a dirty working tree. Never fingerprint secret-bearing data into
|
||||||
|
a report or claim a source identity that was not captured.
|
||||||
|
- Exact command, toolchain/target and required environment or immutable image
|
||||||
|
identity, excluding secrets. Mark missing historical identity as unavailable.
|
||||||
|
- Actual outcome: exit/result counts, fixture versus Alpine versus VM scope,
|
||||||
|
cleanup outcome, limitations and which later edits invalidate the evidence.
|
||||||
|
- Independent reviewer disposition and remaining findings where applicable.
|
||||||
|
|
||||||
|
Do not manufacture missing evidence when migrating old records. Carry forward
|
||||||
|
reported results as historical evidence and state whether they were rerun.
|
||||||
|
Before accepting a milestone, verify the integrated files, close actionable
|
||||||
|
findings, run required gates on that revision and update the acceptance mapping.
|
||||||
|
|
||||||
|
A handoff consists of current task/state/owner, open issue, latest applicable
|
||||||
|
checks, environment prerequisites and one concrete next action. Link diagnostics
|
||||||
|
only if needed to avoid repeating an investigation. It must be possible to resume
|
||||||
|
from the current plan without reconstructing chronology.
|
||||||
|
|
||||||
|
## Migration and ownership
|
||||||
|
|
||||||
|
Preserve the old record in `docs/history/` before compacting existing diagnostic
|
||||||
|
history. Keep scope, acceptance criteria, stable finding IDs, final outcomes,
|
||||||
|
limitations and source links. Clearly label contradictory historical statuses as
|
||||||
|
superseded. Migrate older accepted milestones when they need maintenance; do not
|
||||||
|
rewrite their acceptance merely for uniform formatting.
|
||||||
|
|
||||||
|
The coordinator owns PLANS.md and evidence consolidation. Assign a diagnostics
|
||||||
|
file to one writer at a time, or have workers return concise entries for the
|
||||||
|
coordinator to integrate. Include these record boundaries in dispatch briefs;
|
||||||
|
workers still report actual checks and unresolved issues. New agent configuration
|
||||||
|
may not affect an existing session: supply this document explicitly at its next
|
||||||
|
authorized dispatch/handoff. Do not message other chats without user authorization.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Implementation Plan
|
# Implementation Plan
|
||||||
|
|
||||||
Basis: the approved [specification](SPEC.md). This document defines the order of work without changing the requirements. Specific libraries and serialization formats remain implementation decisions.
|
Basis: the approved [specification](SPEC.md). This document defines the order of work without changing the requirements. Specific libraries and serialization formats remain implementation decisions. Maintain current state, evidence, and diagnostics according to [Execution records and diagnostic workflow](EXECUTION_RECORDS.md); PLANS.md must not become an append-only debugging diary.
|
||||||
|
|
||||||
## 1. Model and configuration
|
## 1. Model and configuration
|
||||||
|
|
||||||
|
|||||||
+146
-11
@@ -78,29 +78,164 @@ Production access is limited and experimentation on the production VPN is prohib
|
|||||||
|
|
||||||
### Alpine environment
|
### Alpine environment
|
||||||
|
|
||||||
- Use `testcontainers` to start separate Alpine Linux server and client containers on a dedicated test network. Keep the image minimal: `wireguard-tools`, `iproute2`, and only any additional tools actually needed by the fixture. Alpine is not a systemd test environment.
|
- Use the approved `testcontainers` development dependency to start a fresh Alpine server and client on a dedicated internal IPv4 network. The RFC1918 bridge subnet `192.168.253.0/24` is reserved for this isolated underlay and must not overlap the synthetic tunnel ranges used by scenarios. Alpine is not a systemd test environment. The host process owns only resources it creates for the current scenario: uniquely named network, server/client containers, and private temporary fixture files. Run the probe synchronously so cleanup does not race a forcibly stopped Testcontainers request. When Testcontainers returns a container handle, RAII drops that owned container on success and recoverable failure; a final bounded cleanup inspects only the scenario's exact names and removes IDs only when the current process's ownership label matches. If a start request times out before returning an ID, an immediate absent inspection cannot prove the daemon will never finish creation. Report that resource status as uncertain, retain its exact name, and do not broaden cleanup.
|
||||||
- Build and cache the fixture image ahead of repeated test runs. Pin the Alpine base/version or digest and document the image recipe; avoid package installation and image rebuilding for every test. Reuse image layers, not mutable containers or secret-bearing volumes between tests.
|
- Pin the Alpine base to a release or digest, record the resolved image identity and package versions, and build/cache the image before repeated suite runs. The runtime image contains only `wireguard-tools`, `iproute2`, BusyBox facilities that are actually used, and justified fixture requirements. It contains no systemd, Cargo registry, compiler, production configuration, generated key, or secret-bearing volume. Reuse image layers only; each scenario gets fresh containers and fresh private data directories.
|
||||||
- The Docker host must provide Linux kernel WireGuard support. Containers share that kernel; installing userspace tools in Alpine does not supply it. Probe prerequisites in the disposable environment and report missing capabilities clearly. Do not automatically load host modules or change host settings.
|
- The Docker-compatible daemon must be explicitly selected as a disposable Linux environment. The suite first identifies the selected endpoint and requires an explicit test-environment opt-in before making Docker API calls. It must not silently use a configured remote context. The daemon host must already provide kernel WireGuard support: containers share that kernel, and Alpine userspace tools do not supply it. A disposable probe verifies WireGuard interface creation and IPv4 address/route operations inside throwaway containers; it must not load modules, alter host sysctls, or create host interfaces. Missing support fails an explicitly requested run. IPv6 capability and traffic are intentionally not tested by M8.
|
||||||
- Grant `NET_ADMIN` in the test containers for their own network namespaces. Do not default to `--privileged`, `SYS_MODULE`, host networking, or shared host PID namespaces. Configure IPv6 for the dedicated test network as needed; exercise both address families.
|
- Testcontainers 0.28.0 is built with only its `blocking` feature; `properties-config` is disabled. Its Docker client therefore reads `DOCKER_HOST` from the process environment and cannot select `tc.host` or `docker.host` from `~/.testcontainers.properties`. The probe requires `DOCKER_HOST` to exactly equal the explicitly selected local Unix endpoint, and its Docker CLI checks pass that same endpoint with `--host`. A mismatch fails before either client is used.
|
||||||
- Run the application operations or a test driver linked to the real library inside the server container, including the approved `rustix` Generic Netlink runtime adapter. A host-side test harness may orchestrate containers, but must not query or modify the host's WireGuard interface as a shortcut.
|
- Give only the server and client containers `NET_ADMIN`, scoped to their own network namespaces. Run them in separate container network and PID namespaces. Do not use `--privileged`, `SYS_MODULE`, host networking, host PID/IPC namespaces, host `/etc/wireguard`, host D-Bus, Docker socket mounts, or production bind mounts. Use a uniquely named internal IPv4 network and choose its private underlay subnet so it does not overlap the tunnel ranges.
|
||||||
- Build the in-container executable for Alpine compatibility, preferably the matching musl target. Do not assume a glibc-linked host binary runs in Alpine. Keep compiler/toolchain layers out of the small runtime fixture image where practical.
|
- The host harness creates and tears down containers/networks and transfers bounded fixture inputs and outputs. Server-side operations run in the server container against its private fixture paths and namespace, using the real Application and `runtime::transport::observe_interface` path. Never query or mutate the host WireGuard interface as a shortcut. Use Testcontainers' supported synchronous runner (`SyncRunner`) and lifecycle/image APIs such as `ImageExt` network configuration only after checking the pinned crate version's API. Fixture commands use the selected endpoint and owned container ID through the bounded Docker CLI transport so a Testcontainers attach stream cannot leave the harness waiting indefinitely. See the [testcontainers 0.28.0 API](https://docs.rs/testcontainers/0.28.0/testcontainers/) and its [`ImageExt`](https://docs.rs/testcontainers/0.28.0/testcontainers/core/trait.ImageExt.html), [`Container`](https://docs.rs/testcontainers/0.28.0/testcontainers/core/struct.Container.html), and [`SyncRunner`](https://docs.rs/testcontainers/0.28.0/testcontainers/runners/trait.SyncRunner.html) references. Keep those APIs in host orchestration; they do not authorize fixture code to control unrelated containers.
|
||||||
- Generate synthetic server/client keys and configurations for each test. Copy fixtures into container-local filesystems. Never mount production files, the host's `/etc/wireguard`, host system D-Bus sockets, or the Docker socket inside fixture containers.
|
- Build only the required server-side test driver for `x86_64-unknown-linux-musl`, or build it in a pinned Alpine/musl builder using the repository's pinned Rust version. A host glibc executable, a target name, or a successful host build is not runtime compatibility evidence. Inspect dynamic requirements and execute a harmless readiness operation in the pinned Alpine image before the suite. Keep the compiler and build cache out of the runtime image. Missing target/linker prerequisites mean the suite cannot run; do not install global tools implicitly.
|
||||||
- Poll explicit readiness and handshake/traffic conditions with deadlines rather than fixed sleeps. Always clean up containers, dedicated networks, and temporary resources after success or failure. Keep diagnostic artifacts redacted.
|
- Keep the in-container driver boundary private to tests. Prefer an explicitly selected `cfg(test)` driver/scenario entry point that invokes the real application and the real Rustix reader; do not add a production apply command or an environment-variable bypass. The fixture apply operation may use explicit `ip`/`wg` commands with argument arrays and fixed fixture inputs, or equivalent direct fixture setup, but it must never execute imported hooks or arbitrary configuration text. It is not the production zbus adapter and all resulting reports must say “fixture apply.”
|
||||||
|
- Preserve staged application authority in one long-lived in-container driver session for each scenario. That driver owns the `Application` instance from client creation through Commit and first delivery, retaining the application's in-memory first-issue eligibility. Reopening the saved store or launching a new process between Commit and first delivery is not a valid substitute: first delivery must remain bound to the creation session, and a reopened process must use password-authorized reissue. After first delivery, the driver may perform fixture apply through the private test seam and then verify through the real observation and normal application verification/persistence path. It must not mark state applied or release reservations directly.
|
||||||
|
- The driver's control channel carries bounded, typed phase requests and redacted phase results only. Do not put passwords, private keys, complete client configurations, QR payloads, or vault plaintext in arguments, environment variables, image metadata, logs, or captured stdout/stderr. Generate WireGuard keys, vault material, and synthetic server/client configurations inside the private server fixture session. Seed only nonsecret driver binaries and fixture scripts with pre-start image copy configuration. Store secret-bearing files in mode-`0600` files under mode-`0700` container directories. Send only opaque path/session identifiers over the control channel. Copy the actual first-delivery file from the server container to a host-private mode-`0600` temporary file, then seed a fresh client container from those exact bytes before it starts. Configure the client from that file and verify its integrity without rendering it in diagnostics. Do not reconstruct or edit the payload to make the client work. The official [files and mounts guide](https://rust.testcontainers.org/features/files/) documents `ImageExt::with_copy_to`, `CopyTargetOptions::with_mode`, and `Container::copy_file_from`; the [exec guide](https://rust.testcontainers.org/features/exec_commands/) documents `Container::exec`. `with_copy_to` packages pre-start content in a TAR; accept only regular paths and reject symlinks. Confirm the pinned 0.28.0 crate supports the chosen sequence before implementation. Never use a host bind mount for secrets.
|
||||||
|
- Generate synthetic keys/configurations per scenario in private fixture storage. Never put real keys/configs or working `Backup/` data in the build context. Diagnostics may include scenario, phase, public identity, status, elapsed time, and fixed redacted failure category, but not private payloads, command output, QR matrices, or `wg show all dump`. Each in-container command runs through `/bin/busybox timeout -s KILL 15 PROGRAM ...` and uses the bounded Docker CLI transport rather than a Testcontainers attach stream. Each CLI call has a 20-second host deadline, which also bounds a stalled exec response. Stdout and stderr are drained while retaining at most 128 bytes for namespace stdout and 1024 bytes for other stdout/stderr; output overflow fails with a fixed redacted category. The in-container timeout category is `In-container fixture command timed out`; if the host deadline expires, command completion inside the owned container is reported as uncertain. Testcontainers 0.28.0 configures a 120-second Docker API request timeout. The probe has at most 12 sequential Testcontainers API requests: two container starts (at most five each) and two removals, plus a conservative allowance for cleanup on partial starts. This gives a 24-minute upper bound for those API requests. Eight bounded fixture CLI calls add at most 160 seconds; daemon/image checks and network create/remove add at most 60 seconds; partial-start inspection/removal adds at most 60 seconds; final cleanup has a 40-second budget. The conservative aggregate bound is 27 minutes. No shorter outer deadline interrupts an in-flight Testcontainers request. If a start request fails before returning a resource ID, the harness reports an uncertain resource status even if immediate inspection finds no matching resource. Poll explicit conditions against monotonic deadlines rather than sleeping a fixed duration. A cleanup error must not replace the original test failure.
|
||||||
|
|
||||||
### Required container scenarios
|
### Required container scenarios
|
||||||
|
|
||||||
1. Start an isolated server and existing client, establish a tunnel, and read real peer state through the runtime adapter.
|
1. Start an isolated server and existing client, establish a tunnel, and read real peer state through the runtime adapter.
|
||||||
2. Create a dual-stack client through the application, Commit, and verify saved files/vault plus unchanged running peer state. Deliver the client configuration and verify that it cannot connect before explicit application.
|
2. Create a client through the application, Commit, and verify saved files/vault plus unchanged running peer state. Deliver the client configuration and verify that it cannot connect before explicit application. M8 checks IPv4 behavior only.
|
||||||
3. Use a test-only fixture operation to apply the saved configuration inside the container. Establish the new tunnel, generate IPv4/IPv6 traffic, and verify handshake and increasing counters without asserting exact byte totals.
|
3. Use a test-only fixture operation to apply the saved configuration inside the container. Establish the new tunnel, generate IPv4 traffic, and verify handshake and increasing counters without asserting exact byte totals. IPv6 capability and traffic are intentionally untested.
|
||||||
4. Delete a client and Commit; verify it is removed from saved data but remains in running state until explicit application, and its address remains reserved. After verified application, check removal, loss of tunnel access, and address release.
|
4. Delete a client and Commit; verify it is removed from saved data but remains in running state until explicit application, and its address remains reserved. After verified application, check removal, loss of tunnel access, and address release.
|
||||||
5. Exercise backups and restoration against container-local data: restoration requires no vault password and does not mutate running state. Verify that applying the restored configuration returns the expected clients.
|
5. Exercise backups and restoration against container-local data: restoration requires no vault password and does not mutate running state. Verify that applying the restored configuration returns the expected clients.
|
||||||
6. Cover mandatory-backup failure and invalid client import with assertions about unchanged protected state and preserved source files.
|
6. Cover mandatory-backup failure and invalid client import with assertions about unchanged protected state and preserved source files.
|
||||||
|
|
||||||
Any fixture apply/restart adapter is test-only. It must not weaken the production requirement for a separate systemd restart or be reported as testing the production `zbus` implementation. If application orchestration uses the fixture service adapter, label those results accordingly.
|
Any fixture apply/restart adapter is test-only. It must not weaken the production requirement for a separate systemd restart or be reported as testing the production `zbus` implementation. If application orchestration uses the fixture service adapter, label those results accordingly.
|
||||||
|
|
||||||
|
### Opt-in contract and command status
|
||||||
|
|
||||||
|
The planned suite is an explicitly named ignored integration target, selected
|
||||||
|
with a single-target command so `--ignored` cannot pick up unrelated privileged
|
||||||
|
tests. The harness must additionally require an explicit environment selection
|
||||||
|
for the disposable daemon before contacting Docker. A missing prerequisite after
|
||||||
|
explicit opt-in is a nonzero test failure with a redacted prerequisite category,
|
||||||
|
never a successful “skipped” test. Ordinary `cargo test`, `--help`, and
|
||||||
|
`--version` must not contact Docker, require root, or inspect host interfaces.
|
||||||
|
|
||||||
|
The runtime image recipe is `tests/containers/alpine/Dockerfile`. Its build
|
||||||
|
context is only `tests/containers/alpine/`; `.dockerignore` is an allowlist for
|
||||||
|
the Dockerfile, package lock, and harmless readiness script. It does not include
|
||||||
|
the repository, `.git`, Cargo data, credentials, `Backup/`, keys, or configs.
|
||||||
|
The image pins the official Alpine 3.22.7 `linux/amd64` manifest
|
||||||
|
`sha256:dc92f017beea8e7f43263073d717f7bc011228c95d5312e440deb4d035bb4eec`.
|
||||||
|
The [official image metadata](https://github.com/docker-library/repo-info/blob/master/repos/alpine/tag-details.md)
|
||||||
|
reports config/image ID
|
||||||
|
`sha256:24ab38714e948bd49d24d566e97c1c104b63f85c2e642685e72393b37cfb9862`.
|
||||||
|
The Alpine v3.22 x86_64 package indexes currently list [`wireguard-tools`
|
||||||
|
1.0.20250521-r0](https://pkgs.alpinelinux.org/packages?branch=v3.22&name=wireguard-tools)
|
||||||
|
and [`iproute2` 6.15.0-r0](https://pkgs.alpinelinux.org/packages?branch=v3.22&name=iproute2).
|
||||||
|
The exact versions of every package in the installed closure are recorded in
|
||||||
|
`packages.lock` and passed to `apk add` as constraints. Alpine branch indexes
|
||||||
|
remain mutable; if an indexed version disappears, the build fails instead of
|
||||||
|
silently selecting a different closure. The image installs only the listed
|
||||||
|
runtime package closure; BusyBox is included in that closure.
|
||||||
|
|
||||||
|
From the repository root, after selecting the disposable daemon, build and cache
|
||||||
|
the image with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker build --pull=false --platform linux/amd64 --tag wireguard-admin-alpine:3.22.7 --file tests/containers/alpine/Dockerfile tests/containers/alpine
|
||||||
|
docker image inspect --format='{{.Id}} {{.Os}}/{{.Architecture}}' wireguard-admin-alpine:3.22.7
|
||||||
|
docker run --rm --network none --read-only --cap-drop ALL wireguard-admin-alpine:3.22.7 /usr/local/bin/wireguard-admin-alpine-check
|
||||||
|
docker run --rm --network none --read-only --cap-drop ALL wireguard-admin-alpine:3.22.7 apk info -v
|
||||||
|
```
|
||||||
|
|
||||||
|
The first command produces daemon-local image tag
|
||||||
|
`wireguard-admin-alpine:3.22.7`; the inspect output records the actual image ID.
|
||||||
|
The final command records the complete installed package closure. On the
|
||||||
|
selected daemon, the image built successfully with ID
|
||||||
|
`sha256:f41cd6b848d8280d4d86bcaceea962e1b3e5a73e9496c6f9d7929470d0e874d5`;
|
||||||
|
`apk info -v` matched the 33 exact constraints in `packages.lock`, and the
|
||||||
|
networkless, read-only, capability-dropped readiness command exited 0. This
|
||||||
|
validates the shell tools in the image only; no WireGuard interface was created
|
||||||
|
and no Rust driver compatibility is claimed.
|
||||||
|
|
||||||
|
M8.4 provides the ignored `#[cfg(test)]` library test
|
||||||
|
`container_driver::alpine_readiness`; libtest selects it with the supported
|
||||||
|
`--ignored --exact` arguments. It checks only that the process is running on
|
||||||
|
Linux x86_64 and does not access or modify an interface. The wrapper invokes
|
||||||
|
only that test with one test thread. M8.4 also provides a test-only fixture
|
||||||
|
command enum for explicit `ip` link/address/route and `wg setconf` operations.
|
||||||
|
It passes argument arrays through BusyBox's 15-second timeout, limits interface,
|
||||||
|
address, route and config-file inputs to typed/validated forms, discards child
|
||||||
|
output, and returns a fixed redacted failure category. `wg setconf` consumes a
|
||||||
|
fixture-local file; imported hooks and arbitrary configuration text are never
|
||||||
|
executed. The application fixture-restart adapter is also `cfg(test)` and
|
||||||
|
continues through the real Rustix observation, saved-peer verification and
|
||||||
|
journaled persistence path. Its result represents fixture apply, not a systemd
|
||||||
|
job.
|
||||||
|
|
||||||
|
M8.5's scenario driver must keep a single `Application` value alive from
|
||||||
|
creation through Commit and first delivery so the in-memory first-issue
|
||||||
|
authorization remains bound to that creation session. It must not reopen the
|
||||||
|
store between those operations. Build the test harness for musl with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo test --locked --lib --no-run --target x86_64-unknown-linux-musl
|
||||||
|
```
|
||||||
|
|
||||||
|
The host lacks the musl target. The official `rust:1.97.1-alpine3.22`
|
||||||
|
linux/amd64 builder manifest is pinned to
|
||||||
|
`sha256:b348cb409ac0a73de15065997a360063cf87465574a15e3e4469862cb8996f02`.
|
||||||
|
On the selected disposable daemon its image ID is
|
||||||
|
`sha256:bc2b74a0f3f942897f678aa466c3cce54048cf1e094893af3596cf43766153c2`;
|
||||||
|
it reports Rust/Cargo 1.97.1 and the installed musl target. From the repository
|
||||||
|
root, build without installing host tooling or copying source into the image:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker run --rm --platform linux/amd64 --mount type=bind,src="$PWD",dst=/src,readonly --mount type=bind,src=/tmp,dst=/out -e CARGO_TARGET_DIR=/out/wga-m8-build-target -w /src rust:1.97.1-alpine3.22@sha256:b348cb409ac0a73de15065997a360063cf87465574a15e3e4469862cb8996f02 cargo test --locked --lib --no-run --target x86_64-unknown-linux-musl
|
||||||
|
```
|
||||||
|
|
||||||
|
The 2026-10-23 build completed in 4m25s and emitted
|
||||||
|
`/tmp/wga-m8-build-target/x86_64-unknown-linux-musl/debug/deps/wireguard_admin-dcc9c812241bb031`.
|
||||||
|
The hash may change after source edits; use Cargo's printed executable path.
|
||||||
|
If `/tmp` has been cleared and an offline rebuild is needed, mount an existing
|
||||||
|
public Cargo registry cache read-only and set `CARGO_HOME` as shown in the
|
||||||
|
[M8.7 evidence](evidence/milestone-8.md). The pinned builder then produced the
|
||||||
|
same driver SHA256 from the unchanged source on 2026-10-25. Missing cached
|
||||||
|
dependencies remain a failed prerequisite until obtained through an approved
|
||||||
|
build setup; an absent driver must never count as a skipped scenario.
|
||||||
|
`file` identified that artifact as a static PIE x86-64 ELF, and `readelf -l`
|
||||||
|
showed no dynamic interpreter. A preliminary networkless, read-only,
|
||||||
|
capability-dropped Alpine run selected its readiness test and passed 1/1 using
|
||||||
|
only a read-only bind of this nonsecret binary. The M8.4 host smoke now copies
|
||||||
|
the current binary into a fresh Testcontainers fixture and selects one ignored
|
||||||
|
`container_driver::alpine_scenario_driver` test through a bounded stdin/stdout
|
||||||
|
channel. It requires bootstrap, Create, Commit, first delivery with activation
|
||||||
|
warning, and verified fixture Apply phase markers. The readiness wrapper executes
|
||||||
|
`PATH --ignored --exact container_driver::alpine_readiness --test-threads=1`;
|
||||||
|
it does not pass a custom argument to the harness. No fixture key or
|
||||||
|
configuration is copied into the builder image or runtime image build context.
|
||||||
|
|
||||||
|
The M8.3 prerequisite and M8.4 Application smoke share one exact ignored host
|
||||||
|
test. After building the current musl artifact, run it on the explicitly selected
|
||||||
|
local daemon with the matching endpoint and daemon ID:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
DOCKER_HOST=unix:///var/run/docker.sock WIREGUARD_ADMIN_TEST_DAEMON_ENDPOINT=unix:///var/run/docker.sock WIREGUARD_ADMIN_TEST_DAEMON_ID=1baad80e-d79d-49ab-b19a-fb00fd3662d7 WIREGUARD_ADMIN_TESTCONTAINERS=1 WIREGUARD_ADMIN_TEST_DRIVER=/tmp/wga-m8-build-target/x86_64-unknown-linux-musl/debug/deps/wireguard_admin-dcc9c812241bb031 cargo test --locked --test testcontainers_wireguard selected_daemon_wireguard_and_application_smoke -- --ignored --exact --test-threads=1
|
||||||
|
```
|
||||||
|
|
||||||
|
The probe uses a uniquely named internal IPv4 bridge and no longer requires
|
||||||
|
Docker IPv6 or IPv6 iptables support. It must pass before M8.4–M8.5 scenarios
|
||||||
|
can be run. On 2026-10-23 the current musl artifact and host smoke passed 1/1
|
||||||
|
in 4.05 seconds on the selected local daemon, and exact-name Docker checks
|
||||||
|
found no leftover test resources. It proves test-only saved-state creation,
|
||||||
|
Commit, first delivery and fixture Apply with real runtime observation; it does
|
||||||
|
not establish a client tunnel or traffic. IPv6 capability and traffic are
|
||||||
|
intentionally untested; production dual-stack behavior remains specified in
|
||||||
|
SPEC.md. The M8.5 traffic slice transfers the server's actual first-delivery
|
||||||
|
file to a fresh client and passed real IPv4 traffic checks. Deletion, restore,
|
||||||
|
import/reissue, and mandatory-backup-failure slices also passed selected-daemon
|
||||||
|
runs, with their slice findings closed. The evidence report records the
|
||||||
|
known-source backup-failure run. On 2026-10-25 the final integrated host gates
|
||||||
|
and two fresh selected-daemon runs passed on one unchanged source fingerprint;
|
||||||
|
the documented unfiltered opt-in command passed as a third fresh run. See the
|
||||||
|
[M8.7 evidence](evidence/milestone-8.md) for identities, counts, durations and
|
||||||
|
limits. These Alpine fixture results do not cover actual systemd/D-Bus jobs or
|
||||||
|
the user-excluded IPv6 capability and traffic checks.
|
||||||
|
|
||||||
### Systemd acceptance and test execution
|
### Systemd acceptance and test execution
|
||||||
|
|
||||||
- Use a disposable Linux VM with WireGuard, systemd, and a system D-Bus to verify the actual `zbus` service adapter, job completion/timeouts, restart verification, and recovery from a selected backup. Do not substitute a mounted host D-Bus or claim Alpine tests cover systemd.
|
- Use a disposable Linux VM with WireGuard, systemd, and a system D-Bus to verify the actual `zbus` service adapter, job completion/timeouts, restart verification, and recovery from a selected backup. Do not substitute a mounted host D-Bus or claim Alpine tests cover systemd.
|
||||||
- Keep container and VM suites out of the default `cargo test` execution. Once implemented, document their exact opt-in commands and prerequisites in this document. Do not list a planned command as available before its test target exists.
|
- Keep container and VM suites out of the default `cargo test` execution. Once implemented, document their exact opt-in commands and prerequisites in this document. Do not list a planned command as available before its test target exists.
|
||||||
- When integration testing is explicitly requested, unavailable Docker, WireGuard support, IPv6, or required capabilities means the suite is not executed or incomplete, not passed. Distinguish prerequisite failures from application failures in the report.
|
- When integration testing is explicitly requested, unavailable Docker, WireGuard support, IPv4 networking, or required capabilities means the suite is not executed or incomplete, not passed. Distinguish prerequisite failures from application failures in the report. IPv6 capability and traffic are intentionally not tested by M8.
|
||||||
- Report unit/fixture results, Alpine real-WireGuard results, and VM real-systemd results separately. Full acceptance requires the relevant real-system checks, not just mocks or successful container startup.
|
- Report unit/fixture results, Alpine real-WireGuard results, and VM real-systemd results separately. Full acceptance requires the relevant real-system checks, not just mocks or successful container startup.
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
# Milestone 8 diagnostic record
|
||||||
|
|
||||||
|
This is a development investigation record for disposable Alpine fixtures. It
|
||||||
|
contains no production observations or secret fixture payloads. Current task
|
||||||
|
state and acceptance remain in [PLANS.md](../../PLANS.md).
|
||||||
|
|
||||||
|
## M8.5 restore fixture reservation mismatch
|
||||||
|
|
||||||
|
- Recorded: 2026-10-24T23:04:57+03:00.
|
||||||
|
- Symptom: the second password-free restore confirmed through `Application`,
|
||||||
|
but its test driver failed a saved-state assertion. The first restore,
|
||||||
|
explicit fixture Apply and renewed IPv4 traffic had already passed.
|
||||||
|
- Bounded reproduction: pinned musl driver built with
|
||||||
|
`cargo test --locked --offline --lib --no-run --target x86_64-unknown-linux-musl`
|
||||||
|
in the approved Rust Alpine builder; selected-daemon ignored test invoked as
|
||||||
|
`cargo test --locked --test testcontainers_wireguard selected_daemon_wireguard_and_application_smoke -- --ignored --exact --test-threads=1`
|
||||||
|
with the explicitly selected local Docker daemon and musl driver path.
|
||||||
|
- Discriminating observations: fixed redacted phase markers reached
|
||||||
|
`application_confirmed` and `precautionary_contents_verified`. Settings,
|
||||||
|
server bytes, vault bytes and target-client presence matched. The actual
|
||||||
|
reservation count was two, the fixture expected one, and both contained the
|
||||||
|
target IPv4 address.
|
||||||
|
- Cause: the fixture expected only the target IPv4 reservation when restoring
|
||||||
|
an archive that removed a saved client with more than one address. Application
|
||||||
|
retained both saved addresses as required while the live peer remained.
|
||||||
|
- Repair: derive the exact expected reservation set from every address on the
|
||||||
|
removed saved client, preserve prior reservations, and filter addresses still
|
||||||
|
saved in the restored set. Keep the IPv4 target membership assertion. This is
|
||||||
|
saved-state bookkeeping; no IPv6 capability or traffic test was run.
|
||||||
|
- Verification: focused reservation-helper regression passed; rebuilt pinned
|
||||||
|
musl driver; selected-daemon scenario passed 1/1 in 72.66s, including both
|
||||||
|
restores and explicit applies. Exact-name test containers and network were
|
||||||
|
absent afterward. Independent code review found no implementation defect;
|
||||||
|
its plan-status finding closed on re-review. Final integrated gates remain.
|
||||||
|
|
||||||
|
Earlier failed diagnostic runs and superseded status are preserved in
|
||||||
|
[the pre-cleanup record](../history/milestone-8-before-workflow-cleanup.md).
|
||||||
|
|
||||||
|
## M8.7 transient execution prerequisites
|
||||||
|
|
||||||
|
- Recorded: 2026-10-25T20:40:51+03:00. The first post-review opt-in run failed
|
||||||
|
before scenario startup because the sandbox denied Docker API access.
|
||||||
|
Running the selected-daemon command with approved sandbox escalation then
|
||||||
|
failed prerequisite inspection because the musl driver artifact under `/tmp`
|
||||||
|
was absent. Exact-name test container/network listings were empty.
|
||||||
|
- A pinned builder rebuild with an empty `/tmp` Cargo cache and `--offline`
|
||||||
|
failed dependency resolution for the already-approved `age` crate. The host's
|
||||||
|
cached public Cargo registry contained it. Mounting that cache read-only into
|
||||||
|
the pinned builder produced the same driver SHA256 as the prior passing run;
|
||||||
|
no dependency or source change was needed.
|
||||||
|
- Three subsequent selected-daemon runs passed with fresh mutable fixtures.
|
||||||
|
Their exact commands, source fingerprint, outcomes, and limits are recorded
|
||||||
|
in [the evidence report](../evidence/milestone-8.md). These initial
|
||||||
|
prerequisite failures are not counted as scenario passes.
|
||||||
|
|
||||||
|
## M8.5 failure fixture legacy-peer deletion
|
||||||
|
|
||||||
|
- Recorded: 2026-10-24T23:27:52+03:00.
|
||||||
|
- Symptom: the selected-daemon scenario passed real reader `NoDevice`/`Timeout`
|
||||||
|
and deterministic Commit backup-failure checks, then stopped before restart
|
||||||
|
backup-failure assertions.
|
||||||
|
- Bounded check: fixed secret-free driver markers localized the stop after
|
||||||
|
parsing the baseline client's identity and before deletion completion. The
|
||||||
|
selected-daemon command is the one recorded in
|
||||||
|
[the evidence report](../evidence/milestone-8.md), with the current pinned
|
||||||
|
musl driver rebuilt for each diagnostic revision.
|
||||||
|
- Cause: the fixture treated `Application::delete_client`'s Boolean as whether
|
||||||
|
the peer was removed. It reports whether an encrypted vault record was
|
||||||
|
removed. The synthetic legacy baseline peer had no such record, so `false`
|
||||||
|
was correct even though the draft peer was deleted.
|
||||||
|
- Repair: assert the baseline identity is absent from the draft after deletion
|
||||||
|
and report the vault-record outcome separately. A focused regression covers
|
||||||
|
deletion of a legacy peer with no vault record.
|
||||||
|
- Verification: focused regression, formatting and Clippy passed; source
|
||||||
|
fingerprint `656707eac2af28f2f3747687e73bb355c4df421663c142d4a7e91409763179b0`
|
||||||
|
remained stable through a pinned musl build and selected-daemon run. That
|
||||||
|
run passed 1/1 in 81.14s; exact-name containers and network were absent.
|
||||||
|
Independent review and final integrated gates are still pending.
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
# Milestone 8 reproducible evidence
|
||||||
|
|
||||||
|
This report records disposable Alpine fixture results. It does not claim
|
||||||
|
production or systemd/D-Bus behavior. Current acceptance state is in
|
||||||
|
[PLANS.md](../../PLANS.md); superseded attempts are in the
|
||||||
|
[history](../history/milestone-8-before-workflow-cleanup.md) and focused
|
||||||
|
[diagnostics](../diagnostics/milestone-8.md).
|
||||||
|
|
||||||
|
## M8.5 import/reissue and preceding scenarios
|
||||||
|
|
||||||
|
- Recorded: 2026-10-24T23:14:00+03:00. Run occurred earlier on 2026-10-24;
|
||||||
|
an exact start timestamp was not captured.
|
||||||
|
- Source identity: Git HEAD
|
||||||
|
`2ccd67d64cd9ef4210ac3dc369473b3849319649` with dirty tracked and
|
||||||
|
untracked M8 files. The reproducible input fingerprint is SHA256
|
||||||
|
`e7aeaf13444ac6350db66cd6c34fb3a46f4ee0723eb18b8ebe0b0054e1672fd0`.
|
||||||
|
It was identical before the pinned driver build and after the real run. The
|
||||||
|
fingerprint was generated by:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
rg --files src tests/support tests/containers tests/testcontainers_wireguard.rs Cargo.toml Cargo.lock | sort | xargs sha256sum | sha256sum
|
||||||
|
```
|
||||||
|
|
||||||
|
- Builder: `rust:1.97.1-alpine3.22@sha256:b348cb409ac0a73de15065997a360063cf87465574a15e3e4469862cb8996f02`,
|
||||||
|
target `x86_64-unknown-linux-musl`; Rust 1.97.1. The locked, offline driver
|
||||||
|
build passed:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker run --rm --platform linux/amd64 --mount type=bind,src=/home/sasa/share/rust/wireguard-admin,dst=/src,readonly --mount type=bind,src=/tmp,dst=/out -e CARGO_TARGET_DIR=/out/wga-m8-build-target -e CARGO_HOME=/out/wga-m8-cargo-home -w /src rust:1.97.1-alpine3.22@sha256:b348cb409ac0a73de15065997a360063cf87465574a15e3e4469862cb8996f02 cargo test --locked --offline --lib --no-run --target x86_64-unknown-linux-musl
|
||||||
|
```
|
||||||
|
|
||||||
|
- Driver: `/tmp/wga-m8-build-target/x86_64-unknown-linux-musl/debug/deps/wireguard_admin-dcc9c812241bb031`,
|
||||||
|
SHA256 `e250d8eb60db0ad262b404624fa7f9d984f807d8688c055e9f8629178c00b151`.
|
||||||
|
- Runtime image: `wireguard-admin-alpine:3.22.7`, image ID
|
||||||
|
`sha256:f41cd6b848d8280d4d86bcaceea962e1b3e5a73e9496c6f9d7929470d0e874d5`,
|
||||||
|
Linux/amd64. Selected disposable local Docker daemon:
|
||||||
|
`unix:///var/run/docker.sock`, ID
|
||||||
|
`1baad80e-d79d-49ab-b19a-fb00fd3662d7`, Docker Server 29.1.3,
|
||||||
|
host Linux `6.18.18-gentoo` x86_64.
|
||||||
|
- Exact selected-daemon command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
DOCKER_HOST=unix:///var/run/docker.sock WIREGUARD_ADMIN_TEST_DAEMON_ENDPOINT=unix:///var/run/docker.sock WIREGUARD_ADMIN_TEST_DAEMON_ID=1baad80e-d79d-49ab-b19a-fb00fd3662d7 WIREGUARD_ADMIN_TESTCONTAINERS=1 WIREGUARD_ADMIN_TEST_DRIVER=/tmp/wga-m8-build-target/x86_64-unknown-linux-musl/debug/deps/wireguard_admin-dcc9c812241bb031 cargo test --locked --test testcontainers_wireguard selected_daemon_wireguard_and_application_smoke -- --ignored --exact --test-threads=1
|
||||||
|
```
|
||||||
|
|
||||||
|
- Result: exit 0, 1 passed, 0 failed, 0 ignored, 25 filtered out, 82.06s.
|
||||||
|
The integrated scenario exercised existing and new IPv4 peers, Commit and
|
||||||
|
delivered-file traffic, saved deletion and explicit fixture Apply, two
|
||||||
|
previewed password-free restores with saved/runtime separation, and import
|
||||||
|
of matched/conflicting/invalid private sources followed by password-gated
|
||||||
|
reissue and traffic from the reissued bytes. It used the real Rustix reader
|
||||||
|
for peer/routes/handshake/counter assertions. Exact-name Docker container
|
||||||
|
and network listings were empty after the run.
|
||||||
|
- Independent disposition: M8.5-IMP-01 closed on re-review after checking all
|
||||||
|
three source-file byte comparisons and the regression for a changed invalid
|
||||||
|
candidate. Earlier traffic, deletion and restore slice findings were closed
|
||||||
|
independently as recorded in the plan.
|
||||||
|
- Limit: this is one Alpine fixture result for the fingerprint above. Later
|
||||||
|
source edits invalidate it as final integrated evidence. User-directed IPv6
|
||||||
|
capability/traffic tests were skipped. No real systemd job was exercised.
|
||||||
|
|
||||||
|
## M8.5 mandatory-backup failure
|
||||||
|
|
||||||
|
- Source-input fingerprint: SHA256
|
||||||
|
`b28dc6e4cb92aa6edf384d966ba5952a8d6d0efe4c22d6a804b67fb2c0297da2`, stable
|
||||||
|
before the pinned driver build and after the real run. Driver SHA256:
|
||||||
|
`ad45dc3f6b0987b075ac522e171c82c6651124c3d01c65c14df90595fe552815`.
|
||||||
|
- Builder: `rust:1.97.1-alpine3.22@sha256:b348cb409ac0a73de15065997a360063cf87465574a15e3e4469862cb8996f02`.
|
||||||
|
Runtime image: `wireguard-admin-alpine:3.22.7`, ID
|
||||||
|
`sha256:f41cd6b848d8280d4d86bcaceea962e1b3e5a73e9496c6f9d7929470d0e874d5`.
|
||||||
|
Selected disposable daemon: `unix:///var/run/docker.sock`, ID
|
||||||
|
`1baad80e-d79d-49ab-b19a-fb00fd3662d7`.
|
||||||
|
- Exact selected-daemon command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
DOCKER_HOST=unix:///var/run/docker.sock WIREGUARD_ADMIN_TEST_DAEMON_ENDPOINT=unix:///var/run/docker.sock WIREGUARD_ADMIN_TEST_DAEMON_ID=1baad80e-d79d-49ab-b19a-fb00fd3662d7 WIREGUARD_ADMIN_TESTCONTAINERS=1 WIREGUARD_ADMIN_TEST_DRIVER=/tmp/wga-m8-build-target/x86_64-unknown-linux-musl/debug/deps/wireguard_admin-dcc9c812241bb031 cargo test --locked --test testcontainers_wireguard selected_daemon_wireguard_and_application_smoke -- --ignored --exact --test-threads=1
|
||||||
|
```
|
||||||
|
|
||||||
|
- Result: exit 0, 1 passed, 0 failed in 81.45s. The scenario required a typed
|
||||||
|
backup error and verified unchanged saved/runtime state, no fixture apply,
|
||||||
|
retained address reservation, and the expected reader missing-interface and
|
||||||
|
deadline errors. Exact-name checks found no remaining test containers or
|
||||||
|
networks.
|
||||||
|
- Limit: this was a completed M8.5 slice result before the final gates and
|
||||||
|
repeated full scenario runs recorded below. IPv6 capability/traffic was
|
||||||
|
excluded by user direction; no real systemd job was exercised.
|
||||||
|
|
||||||
|
## M8.7 integrated gates and repeated Alpine scenario
|
||||||
|
|
||||||
|
- Recorded: 2026-10-25T20:40:51+03:00. Git HEAD remained
|
||||||
|
`2ccd67d64cd9ef4210ac3dc369473b3849319649` with dirty milestone files.
|
||||||
|
The source-input fingerprint command above returned
|
||||||
|
`b28dc6e4cb92aa6edf384d966ba5952a8d6d0efe4c22d6a804b67fb2c0297da2`
|
||||||
|
before the rebuilt driver and after all three successful real runs.
|
||||||
|
- The pinned Alpine runtime image rebuilt entirely from cached layers (ID
|
||||||
|
`sha256:f41cd6b848d8280d4d86bcaceea962e1b3e5a73e9496c6f9d7929470d0e874d5`,
|
||||||
|
linux/amd64). Networkless, read-only, capability-dropped readiness exited 0;
|
||||||
|
`apk info -v` listed the 33 constrained packages. The pinned Rust 1.97.1
|
||||||
|
Alpine builder produced the x86_64 musl test driver in 1m38s using the host's
|
||||||
|
cached public Cargo registry mounted read-only after the earlier `/tmp` cache
|
||||||
|
disappeared. The static PIE driver's SHA256 was
|
||||||
|
`ad45dc3f6b0987b075ac522e171c82c6651124c3d01c65c14df90595fe552815`.
|
||||||
|
The exact rebuild command was:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker run --rm --platform linux/amd64 --mount type=bind,src=/home/sasa/share/rust/wireguard-admin,dst=/src,readonly --mount type=bind,src=/tmp,dst=/out --mount type=bind,src=/home/sasa/.cargo/registry,dst=/out/wga-m8-cargo-home/registry,readonly -e CARGO_TARGET_DIR=/out/wga-m8-build-target -e CARGO_HOME=/out/wga-m8-cargo-home -w /src rust:1.97.1-alpine3.22@sha256:b348cb409ac0a73de15065997a360063cf87465574a15e3e4469862cb8996f02 cargo test --locked --offline --lib --no-run --target x86_64-unknown-linux-musl
|
||||||
|
```
|
||||||
|
|
||||||
|
- Integrated host gates on this source revision: `cargo fmt --all -- --check`,
|
||||||
|
`cargo clippy --locked --all-targets -- -D warnings`, `cargo test --locked`,
|
||||||
|
`cargo build --locked --release`, release `--help` and `--version`, and
|
||||||
|
`git diff --check` all exited 0. Default tests included 258 passing library
|
||||||
|
tests with two explicitly ignored driver tests, five delivery, nine
|
||||||
|
Commit/restore, nine configuration, two runtime, 25 host harness with one
|
||||||
|
ignored container scenario, six TUI, five vault import, and two doctests.
|
||||||
|
Default tests did not execute the real-container scenario.
|
||||||
|
- The selected-daemon command in the preceding section ran twice against this
|
||||||
|
fingerprint with fresh mutable resources: exit 0, 1/1 passed each time,
|
||||||
|
80.77s and 80.77s. The documented unfiltered
|
||||||
|
`cargo test --locked --test testcontainers_wireguard -- --ignored --test-threads=1`
|
||||||
|
form then ran with the same `DOCKER_HOST` and `WIREGUARD_ADMIN_TEST_*` values:
|
||||||
|
exit 0, 1/1 passed in 81.87s; 25 nonignored host tests were filtered out.
|
||||||
|
Each run exercised the full M8.5 scenario, including real IPv4 traffic and
|
||||||
|
Rustix observations, saved/runtime separation, deletion and verified
|
||||||
|
reservation release, both restores, import/reissue, and mandatory-backup
|
||||||
|
failures. Exact-name container and network listings were empty after each.
|
||||||
|
- Independent integrated review found no code defect. Its R1 stale testing and
|
||||||
|
evidence statements, R2 general IPv6 gate, and R3 stale milestone summary
|
||||||
|
were corrected and each closed on independent re-review. M8 acceptance state
|
||||||
|
is recorded in PLANS.md.
|
||||||
|
- Scope: fixture Apply is test-only and does not prove a systemd/D-Bus job.
|
||||||
|
IPv6 capability and traffic were deliberately not tested by user direction;
|
||||||
|
production dual-stack behavior is still specified and unverified here. No
|
||||||
|
production configuration, key, or service was accessed.
|
||||||
@@ -0,0 +1,702 @@
|
|||||||
|
# Milestone 8 historical execution record
|
||||||
|
|
||||||
|
Archived on 2026-10-24 during documentation workflow cleanup. This is a
|
||||||
|
historical snapshot, including superseded and contradictory status statements.
|
||||||
|
Use [PLANS.md](../../PLANS.md) for current state. Evidence below is preserved
|
||||||
|
as previously reported; no checks were rerun by this archival operation.
|
||||||
|
|
||||||
|
### Milestone 8 — Alpine Testcontainers integration
|
||||||
|
|
||||||
|
Status: in progress (2026-10-22; user removed IPv6 capability and traffic
|
||||||
|
testing from M8. The selected disposable daemon remains the test environment;
|
||||||
|
M8.4–M8.5 and real-container acceptance remain unrun).
|
||||||
|
Prerequisites: milestones 1–7 accepted; pinned Rust toolchain; an explicitly
|
||||||
|
selected disposable Docker-compatible Linux environment with kernel WireGuard,
|
||||||
|
scoped NET_ADMIN and IPv4 support; an Alpine-compatible test executable.
|
||||||
|
Missing prerequisites are blockers/incomplete evidence, never passing tests.
|
||||||
|
|
||||||
|
#### Outcome and scope
|
||||||
|
|
||||||
|
Add an opt-in Rust Testcontainers suite using small Alpine server/client images.
|
||||||
|
Exercise the actual application, encrypted vault/persistence/delivery APIs, and
|
||||||
|
M5's real rustix Generic Netlink reader inside the server container against real
|
||||||
|
kernel WireGuard interfaces. Establish real tunnels, generate IPv4 traffic,
|
||||||
|
and prove that saved changes remain separate from runtime application.
|
||||||
|
The user explicitly excluded IPv6 capability and traffic tests on 2026-10-22.
|
||||||
|
Production dual-stack behavior in SPEC.md is unchanged; real IPv6 behavior is
|
||||||
|
therefore unverified by this milestone.
|
||||||
|
|
||||||
|
Read SPEC.md Operations and states, Connection delivery, Backups and restoration,
|
||||||
|
Client screen and statistics, and acceptance criteria; ADRs 0001–0003; TESTING.md,
|
||||||
|
ENVIRONMENT.md, RUNTIME.md, DELIVERY.md, VAULT_FORMAT.md, PERSISTENCE.md,
|
||||||
|
BACKUP_FORMAT.md, DEPENDENCIES.md and current application/worker code.
|
||||||
|
|
||||||
|
This is real-WireGuard integration, not real-systemd acceptance. Alpine fixtures
|
||||||
|
have no systemd; a test-only apply operation may configure the interface explicitly.
|
||||||
|
Actual zbus service job behavior, production restart and VM acceptance remain M9.
|
||||||
|
M7's synthetic TUI/PTY evidence remains separate; M8 does not require live terminal
|
||||||
|
screens, phone QR decoding, public Internet access, external DNS, production
|
||||||
|
configuration, new product commands, or a production apply mode.
|
||||||
|
|
||||||
|
#### Existing code and integration constraints
|
||||||
|
|
||||||
|
- M1–M7 are accepted. The normal executable now starts the management TUI; do not
|
||||||
|
invoke it with host/production settings as a shortcut for a test driver.
|
||||||
|
- Application has open-from-store, setup/import, creation, Commit, delivery/reissue,
|
||||||
|
deletion, backup/list/restore and confirmed restart flows. Use these real APIs,
|
||||||
|
not a parallel shell-only client manager that would leave application behavior
|
||||||
|
untested. Public-key identity and source preservation remain authoritative.
|
||||||
|
- Real observation is `runtime::transport::observe_interface`, currently crate-
|
||||||
|
private, with the validated decoder and bounded rustix transport. Do not use
|
||||||
|
only `wg show` output or a fake observation as evidence that this reader works.
|
||||||
|
- `Application::restart` always invokes the actual zbus path, while injected
|
||||||
|
restart functions are private. Plan an in-crate test seam so a test-only fixture
|
||||||
|
service operation can apply peers and the real observation/persistence path can
|
||||||
|
verify them. Preserve backup ordering and per-attempt authority from M5.
|
||||||
|
- Prefer cfg(test) in-container test scenarios/driver code, compiled for musl and
|
||||||
|
selected explicitly, to reach private adapters without exposing an unauthenticated
|
||||||
|
public apply API. If another arrangement is needed, document its compile-time
|
||||||
|
isolation before implementation. No environment variable may switch production
|
||||||
|
restart to a fake service or bypass password/backup/root checks.
|
||||||
|
- The TUI ApplicationWorker already has synthetic workflow tests. An optional
|
||||||
|
in-container worker scenario may test its real statistics reader, but network
|
||||||
|
acceptance must not depend on automating every terminal key sequence again.
|
||||||
|
- No Testcontainers crate, container image recipe or integration driver is present.
|
||||||
|
Add approved `testcontainers` as a dev-dependency with compatible pinned version
|
||||||
|
and minimal features. Prefer its supported synchronous facade if it avoids a
|
||||||
|
direct executor dependency. Other direct helpers, Docker client crates or
|
||||||
|
Tokio still require prior agreement; transitive dependencies are not new direct
|
||||||
|
approvals. Use Cargo.lock and inspect official APIs for the selected version.
|
||||||
|
- Native tools use system Rust 1.97.1; musl readiness was previously unverified.
|
||||||
|
Recheck rather than assuming rust-toolchain.toml installed targets or a linker.
|
||||||
|
|
||||||
|
#### Context and implementation steps
|
||||||
|
|
||||||
|
Use GPT-6-Sol/medium coordination and GPT-6-Luna/high implementation, fresh review
|
||||||
|
and repairs. Execute bounded tasks sequentially with one writer and explicit file
|
||||||
|
ownership. The coordinator alone updates milestone acceptance and evidence.
|
||||||
|
|
||||||
|
| Task | Owner | Files and deliverable |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| M8.1 | implementer | `docs/TESTING.md`/ENVIRONMENT additions and test support: exact opt-in architecture, prerequisites, image/build and driver contracts |
|
||||||
|
| M8.2 | implementer | `tests/containers/alpine/Dockerfile`, fixture-only setup scripts, musl build recipe: small reproducible cached image and executable compatibility checks |
|
||||||
|
| M8.3 | implementer | `tests/testcontainers_wireguard.rs`, `tests/support/` modules and Cargo files: host orchestration, private network/container ownership, prerequisite checks, bounded execution and cleanup |
|
||||||
|
| M8.4 | implementer | in-crate cfg(test) scenarios/private adapters, test driver coordination: real netlink reads, fixture apply and application state verification inside server |
|
||||||
|
| M8.5 | implementer | container scenarios and TESTING/README documentation: required lifecycle, restore, delivery/import/failure assertions and executed evidence |
|
||||||
|
| M8.6 | reviewer, repairer | Independent isolation, real-adapter coverage and negative-test review; bounded fixes and real-suite re-review |
|
||||||
|
| M8.7 | coordinator | Default and explicit container gates, redacted results, prerequisites/limitations, acceptance and M9 handoff |
|
||||||
|
|
||||||
|
M8.1 defines two execution locations: host harness uses Testcontainers to manage
|
||||||
|
containers; server-side code uses the real application and kernel reader in that
|
||||||
|
container's namespaces. Document how driver sessions persist across staged create,
|
||||||
|
Commit, first delivery and apply phases, and how the client is configured from the
|
||||||
|
actual delivered file. Do not lose the creation session between Commit and first
|
||||||
|
issue or reconstruct password-free authority on reopen.
|
||||||
|
|
||||||
|
Choose a named ignored integration target/scenarios or another explicit opt-in
|
||||||
|
mechanism that default cargo test never starts. The host suite must also opt into
|
||||||
|
all in-container scenario processes. Ensure --ignored does not select unrelated
|
||||||
|
privileged tests. Fail an explicitly requested suite nonzero on missing required
|
||||||
|
prerequisites; never return early with a successful test result labelled skipped.
|
||||||
|
|
||||||
|
M8.2 pins an Alpine base release/digest and records the resolved image identity
|
||||||
|
and package versions. Install only wireguard-tools, iproute2 and minimal fixture
|
||||||
|
requirements. Use BusyBox capabilities when sufficient; justify any extra package
|
||||||
|
in the test recipe. Runtime images contain no Cargo registry/compiler or systemd.
|
||||||
|
Build/cache image layers and compatible driver artifacts before repeated tests;
|
||||||
|
use fresh mutable containers/volumes for each scenario, not reusable dirty servers.
|
||||||
|
Keep build context small with explicit copies/ignore rules, no .git, host Backup,
|
||||||
|
credentials or private configuration. Never bake generated keys into image layers.
|
||||||
|
|
||||||
|
Build server/client driver code for x86_64-unknown-linux-musl or in a pinned
|
||||||
|
Alpine builder matching the toolchain. Inspect dynamic requirements and actually
|
||||||
|
execute a harmless readiness command inside Alpine. Do not rely on a glibc host
|
||||||
|
binary, target name or successful host compilation as runtime compatibility proof.
|
||||||
|
Only build required test targets; avoid pulling compiler/debug layers into the
|
||||||
|
small runtime fixture. No global package installation or toolchain update is
|
||||||
|
implicitly authorized by a missing cross-build prerequisite.
|
||||||
|
|
||||||
|
M8.3 checks Docker API access and explicit disposable-environment selection.
|
||||||
|
Do not use a configured remote daemon without establishing that it is the selected
|
||||||
|
non-production test environment. Probe WireGuard interface creation inside
|
||||||
|
a disposable probe container, never by creating host interfaces or loading modules.
|
||||||
|
Container tools alone do not supply the host kernel module. Refuse to repair
|
||||||
|
prerequisites via SYS_MODULE, privileged mode or host sysctl changes automatically.
|
||||||
|
|
||||||
|
Create a dedicated uniquely named IPv4 network with a test subnet that does
|
||||||
|
not overlap the tunnel ranges. Run server/client as container root with scoped
|
||||||
|
NET_ADMIN in their own network/PID namespaces; no host networking, namespace joins
|
||||||
|
to host, host D-Bus, host /etc/wireguard, Docker socket mount, or production binds.
|
||||||
|
No host UDP publication is needed for container-to-container endpoints. Use
|
||||||
|
container-internal reachable endpoint/port values, not localhost mapped ports.
|
||||||
|
All settings/server/vault/output paths are private fixture directories created
|
||||||
|
with the application's required permissions.
|
||||||
|
|
||||||
|
Use Testcontainers copy/exec APIs or an explicitly safe bounded transport for
|
||||||
|
fixture data. Secret-bearing configs/passwords never enter command arguments,
|
||||||
|
image metadata, public environment variables or raw stdout/stderr logs. Any
|
||||||
|
necessary copied plaintext exists only in private temporary/container storage.
|
||||||
|
Generate keys per scenario. Reports carry public keys, phase/status and redacted
|
||||||
|
categories, not full configurations, QR matrices or `wg show all dump` output.
|
||||||
|
|
||||||
|
Bound image/start/driver/readiness/traffic/teardown operations. Poll explicit
|
||||||
|
conditions with monotonic deadlines, not fixed sleeps. Use unique ownership and
|
||||||
|
RAII/explicit cleanup for containers, networks and volumes on failures as well as
|
||||||
|
success; cleanup must not mask the original failure or delete unrelated resources.
|
||||||
|
Do not default to retain-failed-containers with secrets. Report leftover owned
|
||||||
|
resources if daemon failure prevents cleanup.
|
||||||
|
|
||||||
|
M8.4 keeps fixture `ip`/`wg` calls solely in setup or test-only apply code. It may
|
||||||
|
use wg-quick strip or equivalent explicit fixture setup, but must not execute
|
||||||
|
arbitrary imported hooks. Handle optional DNS/MTU fields in delivered configs via
|
||||||
|
a documented fixture setup, without mutating the delivered payload just to pass.
|
||||||
|
Avoid an unneeded resolvconf dependency by configuring networking explicitly when
|
||||||
|
appropriate. No subprocess apply path is added to production.
|
||||||
|
|
||||||
|
Configure endpoint reachability outside the tunnel before full-tunnel policy so
|
||||||
|
an encrypted UDP endpoint is not recursively routed into its own VPN. Set full
|
||||||
|
IPv4 routes within the client's isolated namespace only. Exercise traffic
|
||||||
|
to server tunnel addresses; an external Internet gateway/DNS is not required and
|
||||||
|
successful tunnel reachability does not claim public Internet egress works.
|
||||||
|
Ensure test success cannot come from the Docker underlay: target tunnel-only
|
||||||
|
addresses, use an explicit interface/route assertion, and verify public counters
|
||||||
|
through the actual rustix reader. Test-only service apply reports must be labelled
|
||||||
|
fixture results, never actual systemd job evidence.
|
||||||
|
|
||||||
|
If fixture restart orchestration verifies and releases application reservations,
|
||||||
|
use the existing private adapter seam with a real backup, fixture apply, actual
|
||||||
|
netlink read/comparison and journaled verification. Do not simply call
|
||||||
|
mark_applied or clear reservations to satisfy assertions. A direct fixture apply
|
||||||
|
outside that orchestration proves only kernel state change; it cannot by itself
|
||||||
|
prove the production successful-restart state transition.
|
||||||
|
|
||||||
|
M8.5 implements these required scenarios, splitting tests where isolation helps:
|
||||||
|
|
||||||
|
1. **Baseline and reader interoperability.** Bring up a server plus an existing
|
||||||
|
client with synthetic configuration, establish real IPv4 tunnel traffic,
|
||||||
|
and observe the configured interface's complete peer set, routes, nonzero fresh
|
||||||
|
handshake and increasing counters through the actual Rust reader. Capture
|
||||||
|
before/after counters, not exact packet-byte totals. Empty/not-yet-handshaken
|
||||||
|
observations must not be misreported as a reader failure or online heuristic.
|
||||||
|
2. **Create, Commit, deliver, then apply.** Use Application to prepare a new client,
|
||||||
|
Commit and save the actual first-delivery file (QR optional). Assert saved
|
||||||
|
server/vault change, mandatory pre-Commit archive, unchanged runtime peers and
|
||||||
|
explicit activation warning. Configure a fresh client from delivered bytes;
|
||||||
|
prove it cannot reach tunnel-only targets before apply. Apply through the
|
||||||
|
test-only boundary, then require real handshake and IPv4 traffic.
|
||||||
|
3. **Saved deletion versus live access.** Delete and Commit an active client;
|
||||||
|
assert current vault record removal, existing backup retention, and unchanged
|
||||||
|
runtime peer/access before apply. Reopen saved state to verify the IP remains
|
||||||
|
reserved. After fixture restart plus real verification/persistence, assert
|
||||||
|
peer removal, failed fresh tunnel traffic and address release. Use a finite
|
||||||
|
negative-check window and establish a working baseline so unrelated setup
|
||||||
|
failure cannot masquerade as successful revocation.
|
||||||
|
4. **Backup/restore separation.** Save a known client set, mutate/Commit, preview
|
||||||
|
and restore the archive without providing a vault password. Assert restored
|
||||||
|
settings/server/vault/state and precautionary backup, plus unchanged kernel
|
||||||
|
state until explicit fixture apply. Then verify the restored peer set and
|
||||||
|
renewed tunnel traffic. Restoration alone never releases pending addresses.
|
||||||
|
5. **Import, reissue and file safety.** Import a valid generated existing client
|
||||||
|
plus invalid/conflicting candidates from private source files. Assert original
|
||||||
|
bytes unchanged, anomalies visible and conflicts blocked. Reopen and reissue
|
||||||
|
a matched client through password-authorized output; verify wrong password
|
||||||
|
fails and a valid delivered file can configure a real tunnel. Preserve first-
|
||||||
|
issue/password boundaries and do not add plaintext source fallback.
|
||||||
|
6. **Failure prevents application.** Produce a deterministic backup failure in the
|
||||||
|
container (not chmod-only when root can bypass it) and assert Commit/fixture
|
||||||
|
restart does not install/apply prepared changes or release addresses. Cover
|
||||||
|
default reader deadline/missing-interface behavior inside its namespace without
|
||||||
|
changing host state. Keep already exhaustive malformed/archive/failure-unit
|
||||||
|
cases in ordinary tests rather than duplicating them in every costly container.
|
||||||
|
|
||||||
|
For all negative reachability assertions, stop/restart client traffic as needed
|
||||||
|
and use new requests; stale timestamps/counters or buffered packets cannot prove
|
||||||
|
continued/revoked access. Use a fixture endpoint/traffic generator with its own
|
||||||
|
bounded error classification. Record that IPv6 traffic is intentionally untested.
|
||||||
|
|
||||||
|
#### Acceptance and validation
|
||||||
|
|
||||||
|
- [ ] M8-A1: documented opt-in commands run an actual Testcontainers suite with
|
||||||
|
pinned/cached Alpine runtime images and compatible in-container driver; default
|
||||||
|
tests/help/version do not start Docker, require root or query host interfaces.
|
||||||
|
- [ ] M8-A2: explicit prerequisite checks reject unavailable daemon/kernel/capability
|
||||||
|
or incompatible artifacts as incomplete/failure; no production or automatic
|
||||||
|
privileged/module/sysctl workaround is used.
|
||||||
|
- [ ] M8-A3: isolated real peers establish IPv4 traffic, and the actual
|
||||||
|
rustix reader reports complete public peer/routes, fresh handshake and traffic
|
||||||
|
increments without relying solely on wg text or fake decoded observations.
|
||||||
|
- [ ] M8-A4: new client creation/Commit/file delivery use real Application and vault
|
||||||
|
APIs; the delivered configuration works only after explicit fixture application,
|
||||||
|
and Commit itself never changes runtime state.
|
||||||
|
- [ ] M8-A5: saved deletion removes only current secret, keeps archives and retains
|
||||||
|
reservation/live peer until explicit apply; actual observation and journaled
|
||||||
|
verification precede reservation release. Fresh traffic is revoked afterward.
|
||||||
|
- [ ] M8-A6: password-free restore replaces the saved set but does not apply it;
|
||||||
|
fixture apply restores real tunnel behavior. Source files remain unchanged and
|
||||||
|
imported conflict/wrong-password outputs remain blocked.
|
||||||
|
- [ ] M8-A7: deterministic mandatory-backup failure prevents protected write/apply;
|
||||||
|
negative traffic and missing-interface results cannot pass due to unrelated
|
||||||
|
broken setup or a silent empty/partial observation.
|
||||||
|
- [ ] M8-A8: fixture adapter cannot be selected in production or bypass root/password/
|
||||||
|
backup controls; no public alternate restart API or runtime environment bypass
|
||||||
|
is introduced. Alpine results explicitly exclude actual zbus/systemd coverage.
|
||||||
|
- [ ] M8-A9: secret-free diagnostics, private copied files, limits/deadlines and
|
||||||
|
cleanup work on both success and failure; only test-owned resources are removed.
|
||||||
|
- [ ] M8-A10: integrated default and real-container gates pass; a clean repeated run
|
||||||
|
reuses image layers while starting fresh mutable scenarios and produces the same
|
||||||
|
behavioral outcomes. Independent review closes actionable isolation/coverage issues.
|
||||||
|
|
||||||
|
Planned host commands once the proposed test target and documented image/driver
|
||||||
|
build steps exist, from repository root:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo fmt --all -- --check
|
||||||
|
cargo clippy --locked --all-targets -- -D warnings
|
||||||
|
cargo test --locked
|
||||||
|
cargo build --locked --release
|
||||||
|
cargo test --locked --test testcontainers_wireguard -- --ignored --test-threads=1
|
||||||
|
./target/release/wireguard-admin --help
|
||||||
|
./target/release/wireguard-admin --version
|
||||||
|
git diff --check
|
||||||
|
```
|
||||||
|
|
||||||
|
Document and execute exact image-build and in-container driver-build commands in
|
||||||
|
TESTING.md before the explicit suite command; include prerequisites and output
|
||||||
|
artifact paths. The in-container target must exist before claiming those commands
|
||||||
|
work. A musl cargo test --no-run build is one possible cfg(test) driver mechanism;
|
||||||
|
resolve its actual compilation/link/dependency implications rather than listing
|
||||||
|
an unverified artifact name. Network downloads/build setup are distinct from
|
||||||
|
locked test execution, and new dependencies require the normal approval policy.
|
||||||
|
|
||||||
|
Record image identities, kernel/runtime/toolchain versions, target architecture,
|
||||||
|
scenario names and actual outcomes/durations/counts. All required scenarios must
|
||||||
|
execute and pass before M8 acceptance; compile-only/default-test success with
|
||||||
|
unavailable Docker is not scoped acceptance of M8. Do not use a successful
|
||||||
|
ignored-test summary as evidence that ignored tests ran. Real systemd remains M9.
|
||||||
|
|
||||||
|
#### Progress
|
||||||
|
|
||||||
|
- [x] Description prepared against accepted M1–M7 and approved Alpine methodology.
|
||||||
|
- [x] M8.1 — implementer `/root/m8_1` prepared the host/container contract in
|
||||||
|
`docs/TESTING.md` and `docs/ENVIRONMENT.md`; documentation link and diff checks
|
||||||
|
passed. Fresh independent reviewer `/root/m8_1_review` raised M8.1-R1, which
|
||||||
|
`/root/m8_1_repair` corrected and the reviewer closed on re-review. Exact
|
||||||
|
driver transport remains an M8.4 decision against the pinned Testcontainers API.
|
||||||
|
- [x] M8.2 — implementer `/root/m8_2` added a pinned Alpine runtime recipe,
|
||||||
|
allowlisted context, package record and harmless readiness script, plus
|
||||||
|
documentation. Fresh reviewer `/root/m8_2_review` raised M8.2-R1/R2;
|
||||||
|
`/root/m8_2_repair` repaired both and independent re-review closed them.
|
||||||
|
Selected-daemon image build and rebuild with 33 exact package constraints,
|
||||||
|
image inspection (`sha256:f41cd6b848d8280d4d86bcaceea962e1b3e5a73e9496c6f9d7929470d0e874d5`,
|
||||||
|
linux/amd64), read-only/networkless/capability-dropped readiness and full
|
||||||
|
`apk info -v` lock match passed. Rust driver execution awaits M8.4.
|
||||||
|
- [x] M8.3 — implementer `/root/m8_3` added the approved Testcontainers 0.28.0
|
||||||
|
blocking development dependency and named ignored host probe. Focused fmt,
|
||||||
|
Clippy and default locked tests passed; explicit selected-daemon run failed
|
||||||
|
nonzero before network creation because Docker lacks IPv6 iptables support.
|
||||||
|
Independent reviewer `/root/m8_3_review` raised M8.3-R1/R2/R3; targeted
|
||||||
|
repairs by `/root/m8_3_repair` and independent re-reviews closed all three.
|
||||||
|
This completes the bounded host-probe implementation, not the blocked
|
||||||
|
real-container M8 acceptance. No tunnel scenario has run.
|
||||||
|
- [x] M8.3 scope revision — `/root/m8_ipv4_revision` changed only the
|
||||||
|
test harness and guidance to use isolated IPv4 networking as requested by the
|
||||||
|
user. Default gates pass; the selected-daemon probe passed 1/1 after R4–R7
|
||||||
|
repairs. Independent `/root/m8_ipv4_review` re-reviews closed all findings.
|
||||||
|
- [x] M8.4 — implementer `/root/m8_4_driver` added the in-crate test-only driver
|
||||||
|
and private runtime/application seams; musl builder prerequisite research is
|
||||||
|
complete from `/root/m8_musl_explore`. The exact ignored readiness and
|
||||||
|
Application smoke run in the pinned Alpine image. Independent
|
||||||
|
`/root/m8_4_review` and `/root/m8_4_host_review` raised R1–R4; targeted
|
||||||
|
repairers closed all findings on re-review. The coordinator rebuilt the final
|
||||||
|
musl test binary, reran the selected-daemon smoke (1/1, 4.05s), and found no
|
||||||
|
leftover named resources. M8.5 scenarios remain pending.
|
||||||
|
- [ ] M8.5 — `/root/m8_5_traffic` owns the first bounded scenario set:
|
||||||
|
delivered-file client transfer, baseline real peer observation, pre-Apply
|
||||||
|
nonreachability and post-Apply IPv4 tunnel/handshake/counter proof. Deletion,
|
||||||
|
restore, import/reissue and backup-failure scenarios follow sequentially;
|
||||||
|
independent review and full real-suite evidence are pending. Local focused
|
||||||
|
support tests currently pass 18/18 with one ignored privileged target, and
|
||||||
|
default `cargo test --locked` passed 250 library tests plus other suites;
|
||||||
|
the current musl driver built in the pinned Alpine builder (5m06s). The first
|
||||||
|
selected-daemon traffic run reached the scenario but failed a stale pre-Apply
|
||||||
|
assertion that expected zero peers even though the new required baseline peer
|
||||||
|
remained applied. The assertion is being changed to require exactly that
|
||||||
|
baseline and exclude the new peer; no traffic acceptance is claimed yet.
|
||||||
|
A later bounded run reached fixture Apply but `preview_restart` rejected the
|
||||||
|
raw baseline peer because it lacked managed alias metadata. The synthetic
|
||||||
|
fixture is being corrected through `ConfigDocument::insert_legacy_metadata`
|
||||||
|
before saved state opens. Cleanup completed after that failed run; real
|
||||||
|
traffic acceptance is still pending.
|
||||||
|
The next real run passed baseline handshake/counters, new-client Create,
|
||||||
|
Commit and delivery, pre-Apply peer exclusion, verified fixture Apply and
|
||||||
|
IPv4 ping. It failed only an overly exact host route-string assertion:
|
||||||
|
actual new-peer routes include product-required `0.0.0.0/0` and `::/0`.
|
||||||
|
The assertion is being made IPv4-specific; no IPv6 capability or traffic
|
||||||
|
test is being added. A final successful traffic suite is still pending.
|
||||||
|
A subsequent reader sample after IPv4 ping showed the new peer's fresh
|
||||||
|
handshake and nonzero received/transmitted counters (916/732 bytes in that
|
||||||
|
run). The host assertion still used the wrong route perspective: on the
|
||||||
|
server, the peer's AllowedIPs are its assigned tunnel hosts, not the client's
|
||||||
|
full-tunnel routes. The assertion is being corrected to the assigned IPv4
|
||||||
|
`/32`; the delivered client file is where `0.0.0.0/0` belongs. No passing
|
||||||
|
end-to-end traffic summary is claimed yet.
|
||||||
|
The host then added an immediate post-Apply/pre-ping reader sample and
|
||||||
|
requires both received and transmitted counters to rise after a new IPv4
|
||||||
|
ping. The worker's final turn stalled after implementing this assertion;
|
||||||
|
coordinator stopped that turn, ran the exact selected-daemon test once and
|
||||||
|
observed 1/1 pass in 16.25s. Exact-name Docker container/network listings
|
||||||
|
were empty afterward. Fresh independent `/root/m8_5_traffic_review` is
|
||||||
|
inspecting the integrated scenario. That reviewer raised M8.5-R1 (P2):
|
||||||
|
the fresh client used a host-derived setconf copy rather than consuming the
|
||||||
|
intact copied first-delivery file, so traffic did not prove the artifact is
|
||||||
|
usable. M8.5-R2 (P2): route assertions accepted unintended extra IPv4
|
||||||
|
prefixes. `/root/m8_5_traffic_repair` owns targeted fixes and real-suite
|
||||||
|
rerun. Exact route assertions apply to IPv4 only per user direction; no IPv6
|
||||||
|
capability or route check is added. This bounded slice is not yet accepted;
|
||||||
|
remaining M8.5 scenarios have not started.
|
||||||
|
The repair added private client-copy byte comparison, in-container
|
||||||
|
`wg-quick strip` consumption of the copied file, and exact IPv4 route-set
|
||||||
|
assertions. The first sandboxed selected-daemon attempt was denied Docker
|
||||||
|
access; the elevated rerun reached the scenario and failed nonzero at the
|
||||||
|
fixed category `copied client configuration could not be safely stripped`.
|
||||||
|
The strip prerequisite/fixture invocation is being diagnosed with synthetic
|
||||||
|
inputs; no secret config was logged and no repaired traffic pass is claimed.
|
||||||
|
Synthetic diagnostics isolated the cause to a copied `.conf` basename longer
|
||||||
|
than wg-quick's 15-character interface-name limit. The repair uses short
|
||||||
|
fixed private names without changing file bytes. The worker turn stalled
|
||||||
|
after editing; coordinator stopped it and ran the repaired selected-daemon
|
||||||
|
test once: 1/1 pass in 14.23s, with no named container/network afterward.
|
||||||
|
Focused host target passed 21 tests with one ignored privileged test; fmt and
|
||||||
|
diff checks passed. Independent R1/R2 re-review was interrupted by an agent
|
||||||
|
usage limit before a finding report, so `/root/m8_5_traffic_review` resumed
|
||||||
|
on 2026-10-24. That independent re-review closed both findings with no new
|
||||||
|
issue: the copied first-delivery bytes are compared privately and consumed
|
||||||
|
by wg-quick in the client namespace; exact IPv4 route sets reject extra
|
||||||
|
prefixes. The bounded baseline/new-client traffic slice is complete as
|
||||||
|
Alpine fixture evidence. `/root/m8_5_delete` now owns saved deletion versus
|
||||||
|
live access. Its local fmt, full locked tests (251 library passed, two
|
||||||
|
ignored), all-target Clippy, release build and focused saved-deletion test
|
||||||
|
passed; pinned musl rebuild passed. The coordinator's exact selected-daemon
|
||||||
|
run failed 1/1 after saved deletion: the baseline client's fresh IPv4 ping
|
||||||
|
stopped before explicit fixture Apply. This is a real-scenario failure, so
|
||||||
|
deletion is not accepted. `/root/m8_5_delete_repair` owns diagnosis and a
|
||||||
|
targeted repair. A fresh continuity probe moved immediately after the prior
|
||||||
|
new-client fixture Apply failed 1/1 on the same selected daemon in 14.25s;
|
||||||
|
the baseline disruption therefore precedes deletion Commit. A separate
|
||||||
|
sandbox-only attempt could not access Docker and supplies no scenario evidence.
|
||||||
|
A bounded selected-daemon diagnostic run then failed 1/1 in 15.52s and
|
||||||
|
reported the baseline peer still present with exact IPv4 route but no fresh
|
||||||
|
handshake, zero reset counters, no learned endpoint and three lost pings
|
||||||
|
after full fixture `wg setconf`. The repair is testing explicit reconnect of
|
||||||
|
the existing client after that fixture apply, then requiring new traffic and
|
||||||
|
reader evidence before continuing the deletion assertions. That reconnect
|
||||||
|
passed far enough to reach deletion Apply. A pinned musl rebuild and exact
|
||||||
|
selected-daemon run reported `RestartStage::Complete`, followed by fixture
|
||||||
|
phase failure; inspection identified the release assertion parsing the
|
||||||
|
client's CIDR as bare `IpAddr`. A targeted typed-address repair and rerun are
|
||||||
|
complete. The next exact selected-daemon run reached post-deletion observation
|
||||||
|
and exposed that full fixture `wg setconf` resets the baseline handshake and
|
||||||
|
counters again; the harness now proves the exact retained/deleted peer set
|
||||||
|
immediately, then reconnects the retained client and requires fresh traffic
|
||||||
|
and reader counter deltas while fresh traffic from the deleted client fails.
|
||||||
|
The coordinator's integrated exact run passed 1/1 in 24.88s, host support
|
||||||
|
tests passed 22 with one privileged test ignored, focused CIDR validation
|
||||||
|
passed, fmt/diff checks passed, and exact-name Docker container/network
|
||||||
|
listings were empty. Fresh independent `/root/m8_5_delete_review` raised
|
||||||
|
M8.5-R3 (P2): the negative ping helper treats any Docker CLI nonzero exit,
|
||||||
|
including failed `docker exec`, as lost tunnel traffic. `/root/m8_5_delete_repair`
|
||||||
|
is changing it to require a bounded ping summary with transmitted packets and
|
||||||
|
zero received packets. The repaired host support target passed 23 tests with
|
||||||
|
one ignored, the coordinator's exact selected-daemon rerun passed 1/1 in
|
||||||
|
42.96s, and exact-name container/network listings were empty. Independent
|
||||||
|
`/root/m8_5_delete_review` closed R3 on re-review with no new finding:
|
||||||
|
completed zero-reply traffic is distinguished from Docker execution and
|
||||||
|
timeout errors. The saved-deletion bounded slice is accepted as Alpine IPv4
|
||||||
|
fixture evidence. `/root/m8_5_restore` implemented the two-restore scenario:
|
||||||
|
pre-delete archive re-adds a saved client while runtime remains baseline-only;
|
||||||
|
an earlier baseline archive later removes a saved live client and retains its
|
||||||
|
address reservation until verified fixture Apply. Local fmt, focused tests,
|
||||||
|
locked full tests, all-target Clippy, release build and diff check passed.
|
||||||
|
Pinned musl rebuild passed, but the coordinator's first selected-daemon run
|
||||||
|
failed 1/1 after 7.47s because the host archive-name parser selected the
|
||||||
|
driver's `status=started` line rather than its completed result line. The
|
||||||
|
targeted parser repair passed focused tests and the next real run advanced
|
||||||
|
through the first password-free restore, explicit Apply and renewed restored
|
||||||
|
client traffic. It failed 1/1 after 58.28s at the second restore of the
|
||||||
|
baseline archive (`phase=restore status=started` only). A separate repairer
|
||||||
|
is diagnosing that in-container phase with bounded diagnostics before another
|
||||||
|
pinned musl rebuild, selected-daemon rerun and independent review.
|
||||||
|
Import/reissue and backup-failure slices remain pending.
|
||||||
|
- [ ] M8.6 — independent implementation reviews and repairs complete for
|
||||||
|
M8.1–M8.3 only; M8.4–M8.5 and integrated real-scenario review pending.
|
||||||
|
- [ ] M8.7 — prior default coordinator gates pass; the revised explicit
|
||||||
|
real-container gate and remaining scenarios are pending. Acceptance incomplete.
|
||||||
|
|
||||||
|
#### Review and repair
|
||||||
|
|
||||||
|
Record stable finding IDs, iteration, location/impact,
|
||||||
|
owner, fix evidence and disposition. Review actual namespace/daemon targeting,
|
||||||
|
secret transport/logs, capability/mount boundaries, endpoint/full-route setup,
|
||||||
|
real-reader invocation and state-persistence proof. Re-review negative tests so
|
||||||
|
underlay connectivity or an unrelated failed setup cannot produce false success.
|
||||||
|
The user-authorized IPv6 testing exclusion applies only to M8 integration tests;
|
||||||
|
do not replace real IPv4 observations with mocks.
|
||||||
|
|
||||||
|
M8.1 R1 review dispatched after coordinator inspected the two-file documentation
|
||||||
|
diff. M8.1-R1 (low): `docs/TESTING.md` linked `SyncRunner` under the wrong
|
||||||
|
Testcontainers module; repair changed the link to `runners/trait.SyncRunner.html`.
|
||||||
|
Independent re-review closed the finding. Both rounds reported `git diff --check`
|
||||||
|
passing on the M8.1 docs; link/consistency checks passed. No Docker evidence.
|
||||||
|
|
||||||
|
M8.2 R1 review of the actual image recipe/docs raised two P2 findings:
|
||||||
|
M8.2-R1 (`runtime-check.sh`): planned libtest artifact cannot accept the
|
||||||
|
script's custom `--readiness` argument. M8.2-R2 (Dockerfile/package record):
|
||||||
|
direct package pins still resolve dependencies from a mutable Alpine repository,
|
||||||
|
and the record/inspection omits the installed closure. Targeted repair
|
||||||
|
`/root/m8_2_repair` addressed both. Reviewer confirmed the pinned direct package
|
||||||
|
versions and Alpine platform digest from primary indexes, plus passing shell
|
||||||
|
syntax, documentation link and diff checks. R1 re-review closed both recipe
|
||||||
|
findings after the repair. `runtime-check.sh` now uses a libtest-compatible
|
||||||
|
exact ignored selector; the test itself remains pending M8.4. The full installed
|
||||||
|
closure was recorded as 33 exact constraints; a second build succeeded with the
|
||||||
|
lock, and read-only shell readiness and inspection passed on the selected daemon.
|
||||||
|
The reviewer did not independently rerun the Docker commands. Coordinator also
|
||||||
|
ran a disposable `--network none --cap-add NET_ADMIN` Alpine probe that created
|
||||||
|
and removed `wg-m8-probe` and added an IPv6 address inside that namespace
|
||||||
|
(exit 0); no host interface, module, or sysctl operation was performed.
|
||||||
|
|
||||||
|
M8.3 R1 review raised M8.3-R1 (P2, `tests/testcontainers_wireguard.rs`): the
|
||||||
|
outer timeout detaches a worker that may later create/hold test resources, so
|
||||||
|
timeout cleanup is not guaranteed. First repair used a killed child and
|
||||||
|
label-verified cleanup, but independent re-review kept R1 open because a
|
||||||
|
Testcontainers request can run for 120 seconds and finish after the 75-second
|
||||||
|
kill/cleanup pass. Second repair made the probe synchronous, retained finite
|
||||||
|
per-request limits, added conservative aggregate documentation and explicitly
|
||||||
|
reports uncertain late daemon completion. Independent re-review closed R1.
|
||||||
|
The same re-review raised M8.3-R2 (P2, `container_harness.rs`): fixture exec
|
||||||
|
stream reads can block indefinitely if a command hangs, invalidating the
|
||||||
|
documented aggregate bound. First R2 repair wrapped commands in BusyBox
|
||||||
|
`timeout`, capped retained output and drained streams, but independent re-review
|
||||||
|
kept R2 open because the host attach read can still block after the stream is
|
||||||
|
established. Coordinator separately verified BusyBox timeout in the cached
|
||||||
|
Alpine image with a harmless delayed command (one-second elapsed inside,
|
||||||
|
status 137); this does not prove the host stream is bounded. Second R2 repair
|
||||||
|
uses a bounded Docker CLI exec transport and independent re-review closed R2.
|
||||||
|
That re-review raised M8.3-R3 (P2, `container_process.rs`): the CLI exit code
|
||||||
|
for an in-container timeout was discarded, so a timed-out command could be
|
||||||
|
misclassified as a generic capability failure. Targeted repair mapped CLI exit
|
||||||
|
codes 124/137/143 to the fixed timeout category, and independent re-review
|
||||||
|
closed R3. No open M8.1–M8.3 review finding remains. The host-side bounded CLI
|
||||||
|
exec path is not runtime-tested because network creation fails first.
|
||||||
|
The selected-daemon explicit harness failed before network/container creation
|
||||||
|
with `Docker host lacks required IPv6 iptables support`; separate disposable
|
||||||
|
regular, internal and routed dual-stack network attempts failed on missing
|
||||||
|
IPv6 NAT, `DOCKER-INTERNAL` filter and `DOCKER` filter chains respectively.
|
||||||
|
No host networking, firewall, module, or sysctl remediation was attempted.
|
||||||
|
|
||||||
|
M8.4 R1 independent review of `src/container_driver.rs` raised M8.4-R1 (P1):
|
||||||
|
the only runnable ignored driver test is harmless readiness; no bounded
|
||||||
|
in-container phase protocol retains an Application across create, Commit,
|
||||||
|
delivery and fixture apply, so the new seam cannot yet reach real Rustix
|
||||||
|
observation or lifecycle assertions. M8.4-R2 (P2): `SetWireGuard` lexical path
|
||||||
|
validation allows a symlink under the fixture root to escape. Focused 7 driver
|
||||||
|
argument tests passed, but no live fixture command ran. Targeted repair is
|
||||||
|
pending; no M8.4 acceptance is claimed.
|
||||||
|
|
||||||
|
M8.4 R1/R2 first repair by `/root/m8_4_repair` added an ignored
|
||||||
|
`alpine_scenario_driver`, a bounded typed stdin/stdout phase protocol retaining
|
||||||
|
one Application through Create, Commit, first delivery and fixture Apply, plus
|
||||||
|
real Rustix verification through the test-only restart seam. It also added
|
||||||
|
symlink component rejection and a regular-file/confined-path check for
|
||||||
|
`wg setconf`; 10 focused driver tests, default tests (244 library tests, two
|
||||||
|
ignored), fmt, all-target Clippy, release build and diff check passed. One
|
||||||
|
initial default run had a transient TUI `Busy` failure; immediate rerun passed.
|
||||||
|
Independent `/root/m8_4_review` re-review closed R2. It kept R1 open (P2):
|
||||||
|
the existing host harness has no bounded stdin channel to invoke the driver,
|
||||||
|
and the protocol does not yet cover M8.5's remaining lifecycle operations.
|
||||||
|
The next bounded repair is host protocol transport and a real in-container
|
||||||
|
Create/Commit/Deliver/Apply smoke path; M8.5 owns the additional scenarios.
|
||||||
|
After the 2026-10-23 daemon/session interruption, coordinator inspection found
|
||||||
|
no host-transport edits from the interrupted worker. `/root/m8_4_host_repair`
|
||||||
|
was dispatched with ownership of the host harness and test-only driver to
|
||||||
|
resume R1; no scenario acceptance is inferred from the prior musl readiness.
|
||||||
|
The worker added bounded `docker exec -i` phase exchange and a private synthetic
|
||||||
|
fixture bootstrap. Its current musl driver rebuilt in the pinned Alpine builder
|
||||||
|
in 3m04s; focused host/driver checks and all-target Clippy passed. On the
|
||||||
|
selected daemon, a first smoke attempt failed before application phases with a
|
||||||
|
generic Docker category and left no owned resources. A diagnostic rerun passed
|
||||||
|
link-up, IPv4 address setup, binary copy and chmod, then failed inside the
|
||||||
|
application driver protocol. The failure stage is being isolated with fixed
|
||||||
|
redacted phase markers; neither run is scenario acceptance. The worker also
|
||||||
|
changed generated settings/server secret files to mode 0600 at creation and
|
||||||
|
discovers the fixture endpoint inside the server namespace.
|
||||||
|
The phase markers subsequently localized the error to Create: the synthetic
|
||||||
|
server document lacked `[Interface] Address`, which the actual application
|
||||||
|
requires for server address ownership. The fixture now includes IPv4/IPv6
|
||||||
|
server addresses in saved configuration while removing the wg-quick-only
|
||||||
|
`Address` directive from a private 0600 copy passed to `wg setconf`. The
|
||||||
|
authoritative saved file is unchanged. This is fixture setup, not an IPv6
|
||||||
|
capability or traffic check. A unique per-apply copy and exact cleanup are
|
||||||
|
being added for repeated M8.5 apply phases; the corrected real smoke rerun
|
||||||
|
is pending. One subsequent scenario process exited zero, but libtest captured
|
||||||
|
its success output because the host invocation omitted `--nocapture`, so the
|
||||||
|
host could not verify the expected phase sequence. This is not accepted as
|
||||||
|
scenario evidence; the host invocation is being repaired and rerun.
|
||||||
|
The completed host repair then passed the explicit selected-daemon smoke
|
||||||
|
(1/1, 3.80s), and the coordinator independently reran it (1/1, 3.60s) with
|
||||||
|
no `wga-m83` container/network on follow-up listing. It exercised one exact
|
||||||
|
ignored driver test and observed Create, Commit, first delivery activation
|
||||||
|
warning, and fixture Apply with real Rustix verification. Final worker default
|
||||||
|
gates passed: fmt, all-target Clippy, `cargo test --locked` (247 library tests,
|
||||||
|
two ignored), release build, focused driver/harness checks and diff check.
|
||||||
|
Fresh independent `/root/m8_4_host_review` found M8.4-R3 (P2: a successful
|
||||||
|
`wg setconf` followed by cleanup failure was reported as not submitted) and
|
||||||
|
M8.4-R4 (P2: preview or partial-write failure could leave a secret-bearing
|
||||||
|
setconf copy). `/root/m8_4_cleanup_repair` separated setconf submission from
|
||||||
|
copy-cleanup outcome and added an armed cleanup guard; focused 15 driver tests,
|
||||||
|
default tests (250 library tests, two ignored), fmt, all-target Clippy, release
|
||||||
|
build and diff check passed. Independent `/root/m8_4_host_review` re-review
|
||||||
|
closed R3/R4 with no new finding. The current test-only driver is being rebuilt
|
||||||
|
for a fresh Alpine smoke; M8.4 is not yet accepted and M8.5 has not started.
|
||||||
|
|
||||||
|
2026-10-23 resume: the prior `/root/m8_4_repair` turn hit an agent usage limit
|
||||||
|
before changing files; the same GPT-6-Luna/high repairer was resumed for R1/R2.
|
||||||
|
The official `rust:1.97.1-alpine3.22` linux/amd64 builder manifest was resolved
|
||||||
|
to `sha256:b348cb409ac0a73de15065997a360063cf87465574a15e3e4469862cb8996f02`,
|
||||||
|
pulled onto the selected disposable daemon, and inspected as image ID
|
||||||
|
`sha256:bc2b74a0f3f942897f678aa466c3cce54048cf1e094893af3596cf43766153c2`.
|
||||||
|
A read-only/networkless/capability-dropped container reported rustc/Cargo
|
||||||
|
1.97.1 and installed `x86_64-unknown-linux-musl`; no application harness has
|
||||||
|
yet been used for a scenario. A subsequent read-only source-mounted builder
|
||||||
|
`cargo test --locked --lib --no-run --target x86_64-unknown-linux-musl`
|
||||||
|
completed in 4m25s and produced a 114 MiB static PIE test binary under
|
||||||
|
`/tmp/wga-m8-build-target/.../wireguard_admin-dcc9c812241bb031`.
|
||||||
|
`file`/`readelf` showed no dynamic interpreter. A networkless, read-only,
|
||||||
|
capability-dropped run in the pinned Alpine runtime selected only
|
||||||
|
`container_driver::alpine_readiness` and passed 1/1. This proves the binary
|
||||||
|
loads in Alpine; no real `wg`, application scenario, or Rustix observation ran.
|
||||||
|
|
||||||
|
After R3/R4 repair, coordinator rebuilt the current musl harness in the pinned
|
||||||
|
builder (`cargo test --locked --lib --no-run --target x86_64-unknown-linux-musl`,
|
||||||
|
3m22s), then ran the exact ignored host smoke with matching selected daemon ID
|
||||||
|
and `WIREGUARD_ADMIN_TEST_DRIVER` pointing at that artifact. It passed 1/1 in
|
||||||
|
4.05s and verified the test-only Create/Commit/first delivery/fixture Apply
|
||||||
|
phase sequence through the real Application and Rustix observation path.
|
||||||
|
`docker ps -a --filter name=wga-m83` and `docker network ls --filter name=wga-m83`
|
||||||
|
both returned empty afterward. This is Alpine fixture evidence; no client
|
||||||
|
tunnel traffic, restore/delete/import, or actual systemd job ran yet.
|
||||||
|
|
||||||
|
After the user excluded IPv6 testing, `/root/m8_ipv4_revision` changed the
|
||||||
|
probe and M8 test guidance to isolated IPv4 while keeping SPEC.md untouched.
|
||||||
|
The revised default suite, fmt, all-target Clippy, release build and diff check
|
||||||
|
passed. The elevated selected-daemon probe created its IPv4 network, but its
|
||||||
|
first Testcontainers container start failed with a generic redacted category;
|
||||||
|
exact-name inspection found no owned resources afterward. A coordinator-only
|
||||||
|
disposable `docker run` on a separately named internal IPv4 network succeeded
|
||||||
|
with `NET_ADMIN`, and that network was removed. Independent reviewer
|
||||||
|
`/root/m8_ipv4_review` raised M8.3-R4 (P2: swallowed Testcontainers error
|
||||||
|
prevents diagnosing this start failure) and M8.3-R5 (P2: route read did not
|
||||||
|
exercise IPv4 route mutation claimed by the guidance). `/root/m8_ipv4_repair`
|
||||||
|
added fixed typed error categories and route add/assert/delete; independent
|
||||||
|
re-review closed R4/R5 but the real route commands remain unrun because the
|
||||||
|
probe still fails at container creation. It raised M8.3-R6 (P2: the `PullImage`
|
||||||
|
category conceals that any Docker create 404 triggers Testcontainers' image-pull
|
||||||
|
fallback). `/root/m8_ipv4_repair_r6` owns the category repair and diagnosis.
|
||||||
|
The repair changed it to a fixed `container creation not-found image fallback
|
||||||
|
failed` category with redaction regression; independent `/root/m8_ipv4_review`
|
||||||
|
re-review closed R6. The explicit probe still fails there, before runtime route
|
||||||
|
commands. No open scoped review finding remains, but actual M8.3 probe
|
||||||
|
acceptance has not passed.
|
||||||
|
The selected daemon is Docker 29.1.3, API 1.52; a direct Docker CLI create/start
|
||||||
|
using the fixture image, internal network, `NET_ADMIN`, label, entrypoint and
|
||||||
|
command succeeded and exact resources were removed. Testcontainers 0.28.0 uses
|
||||||
|
Bollard 0.21.1 (default API 1.53); this was investigated, but the source-level
|
||||||
|
image-name override below explains the observed create 404 without an API
|
||||||
|
version change. No real tunnel or application scenario has run.
|
||||||
|
|
||||||
|
Coordinator inspection of pinned Testcontainers 0.28.0 identified M8.3-R7
|
||||||
|
(P1, startup blocker): the host harness used `ImageExt::with_name` for unique
|
||||||
|
container names, but that method overrides the image name. The nonexistent
|
||||||
|
image explains the create 404 and pull fallback. `with_container_name` is the
|
||||||
|
intended API. `/root/m8_name_repair` owns a narrow repair and live-probe
|
||||||
|
regression; `with_container_name` and descriptor/name/owner assertions are in
|
||||||
|
place. Independent `/root/m8_ipv4_review` re-review closed R7. The repairer's
|
||||||
|
elevated selected-daemon probe passed (1/1); a concurrent
|
||||||
|
coordinator probe collided on the fixed subnet, then a separate coordinator
|
||||||
|
rerun passed (1/1, 1.46 seconds) with cleanup. M8.3 prerequisite acceptance
|
||||||
|
is satisfied for the user-scoped IPv4 test path; no application scenario ran.
|
||||||
|
|
||||||
|
#### Evidence and handoff
|
||||||
|
|
||||||
|
Planning inspected current Cargo/module/test layout, Application public/private
|
||||||
|
restart and delivery boundaries, runtime reader visibility, M7 acceptance record,
|
||||||
|
and current TESTING/ENVIRONMENT/runtime documentation. Existing M7 evidence is
|
||||||
|
synthetic workflow/PTY and ordinary library integration; it is not a real tunnel.
|
||||||
|
This planning task did not rerun Cargo tests, contact Docker, probe kernel support,
|
||||||
|
start containers, install toolchains/packages, add dependencies, or inspect keys.
|
||||||
|
|
||||||
|
2026-10-22 execution preparation: the active coordinator session `turn_context`
|
||||||
|
reported `gpt-6-sol`/`medium`; project role files and explicit dispatch select
|
||||||
|
`gpt-6-luna`/`high`. M1–M7 are accepted in this plan. Native rustc/Cargo are
|
||||||
|
1.97.1, rustfmt 1.9.0 and Clippy 0.1.97. The host lacks rustup, a musl linker,
|
||||||
|
and the musl standard-library directory. The default local Docker daemon is
|
||||||
|
reachable with approved access and identifies as Gentoo Linux, ID
|
||||||
|
`1baad80e-d79d-49ab-b19a-fb00fd3662d7`; the user explicitly selected it as
|
||||||
|
the disposable M8 environment. The scoped container WireGuard/IPv6 capability
|
||||||
|
probe described above passed. No host WireGuard probe ran. The pre-existing
|
||||||
|
expanded M8 description edit is preserved.
|
||||||
|
|
||||||
|
2026-10-22 integrated coordinator gates on the final M8.1–M8.3 revision:
|
||||||
|
`cargo fmt --all -- --check`, `cargo clippy --locked --all-targets -- -D warnings`,
|
||||||
|
`cargo test --locked`, `cargo build --locked --release`, release `--help` and
|
||||||
|
`--version`, documentation local-link check, and `git diff --check` all exited
|
||||||
|
zero. The default test run passed 235 library tests plus integration/doc tests;
|
||||||
|
the Testcontainers target had 8 passing local support tests and one ignored
|
||||||
|
privileged probe. The ignored count is not container evidence. The exact named
|
||||||
|
probe was then explicitly selected with matching local Unix endpoint and daemon
|
||||||
|
ID; it ran one test and exited 101 with `Docker host lacks required IPv6 iptables
|
||||||
|
support`. The first sandboxed attempt could not access the socket, so the
|
||||||
|
coordinator reran it with approved elevated access; only the latter establishes
|
||||||
|
the daemon result. An elevated `docker network ls` and `docker ps -a` afterward
|
||||||
|
showed no `wga-m83` network or container. Existing unrelated resources were
|
||||||
|
untouched. No real peer traffic, netlink observation, application lifecycle,
|
||||||
|
in-container Rust driver, or systemd behavior was verified.
|
||||||
|
|
||||||
|
2026-10-22 user scope change: skip IPv6 capability and traffic tests. The prior
|
||||||
|
dual-stack network failure is historical evidence, no longer an M8 blocker.
|
||||||
|
Use the already selected local disposable daemon with a unique isolated IPv4
|
||||||
|
bridge and scoped `NET_ADMIN`; run the revised named probe. An elevated
|
||||||
|
coordinator run reached container creation but failed after 5.9 seconds with
|
||||||
|
`Testcontainers could not start an owned fixture container`; the fresh IPv4
|
||||||
|
network and container were absent on follow-up inspection. Investigate this
|
||||||
|
failure through scoped implementation/review before claiming the probe passes.
|
||||||
|
Do not modify this
|
||||||
|
host's firewall/modules/sysctls. Then settle the Alpine-compatible in-crate driver
|
||||||
|
build/runtime path (host musl standard library/linker remain absent, so a pinned
|
||||||
|
Alpine builder may be needed), execute M8.4–M8.5 sequentially, independently
|
||||||
|
review/repair the complete scenarios, and rerun all M8 acceptance gates.
|
||||||
|
|
||||||
|
#### Decisions and discoveries
|
||||||
|
|
||||||
|
The production reader is bounded rustix netlink, not wireguard-uapi, and the
|
||||||
|
restart adapter is private zbus. M8 must test that actual reader and preserve
|
||||||
|
production API boundaries rather than resurrect a discarded library path or
|
||||||
|
expose a fake-systemd switch. A test-only service apply plus real comparison is
|
||||||
|
useful integration evidence but does not validate the systemd adapter itself.
|
||||||
|
Image layers may be cached for speed/space while secret-bearing container state
|
||||||
|
must be fresh. The container host shares its kernel with Alpine, so the fixture
|
||||||
|
cannot manufacture missing kernel support by installing wireguard-tools.
|
||||||
|
|
||||||
|
Primary implementation references (check selected versions at dispatch):
|
||||||
|
[Testcontainers requirements](https://rust.testcontainers.org/system_requirements/docker/),
|
||||||
|
[Testcontainers image options](https://docs.rs/testcontainers/latest/testcontainers/core/trait.ImageExt.html),
|
||||||
|
and [WireGuard namespaces](https://www.wireguard.com/netns/).
|
||||||
|
|
||||||
|
#### Outcome
|
||||||
|
|
||||||
|
M8.1–M8.3 were implemented and independently reviewed, with all original
|
||||||
|
findings closed. The user removed IPv6 testing from M8, and an IPv4-only harness
|
||||||
|
revision is in progress. M8.4–M8.5 and real-container acceptance remain pending;
|
||||||
|
M8 is not accepted. Milestone 9 must not start until M8 acceptance; actual
|
||||||
|
systemd/VM validation remains separate and pending.
|
||||||
|
|
||||||
+27
@@ -1462,6 +1462,33 @@ impl Application {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test-only adapter boundary for Alpine fixture apply. The supplied
|
||||||
|
/// callback must report a completed fixture operation; observation and
|
||||||
|
/// verification still use the real Rustix reader and normal persistence
|
||||||
|
/// path. This method is not present in production builds.
|
||||||
|
#[cfg(test)]
|
||||||
|
#[allow(dead_code)] // Consumed by the M8.5 in-container scenario driver.
|
||||||
|
pub(crate) fn restart_with_fixture_apply(
|
||||||
|
&mut self,
|
||||||
|
preview: RestartPreview,
|
||||||
|
choice: RestartChoice,
|
||||||
|
fixture_apply: impl FnOnce(&str, RestartDeadline) -> RestartExecution,
|
||||||
|
) -> RestartReport {
|
||||||
|
self.restart_with_adapters(
|
||||||
|
preview,
|
||||||
|
choice,
|
||||||
|
fixture_apply,
|
||||||
|
|interface, timeout| {
|
||||||
|
runtime::transport::observe_interface(
|
||||||
|
interface,
|
||||||
|
timeout,
|
||||||
|
runtime::netlink::DecodeLimits::default(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|store, saved, backup| store.install(saved, backup),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn restart_with_adapters_and_clock(
|
fn restart_with_adapters_and_clock(
|
||||||
&mut self,
|
&mut self,
|
||||||
preview: RestartPreview,
|
preview: RestartPreview,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -10,3 +10,8 @@ pub mod startup;
|
|||||||
pub mod tui;
|
pub mod tui;
|
||||||
pub mod vault;
|
pub mod vault;
|
||||||
pub mod wireguard;
|
pub mod wireguard;
|
||||||
|
|
||||||
|
// This driver is compiled only into the library test harness. In particular,
|
||||||
|
// the normal executable has no fixture apply path.
|
||||||
|
#[cfg(test)]
|
||||||
|
mod container_driver;
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
**
|
||||||
|
!Dockerfile
|
||||||
|
!.dockerignore
|
||||||
|
!runtime-check.sh
|
||||||
|
!packages.lock
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# linux/amd64 platform manifest for the official Alpine 3.22.7 image.
|
||||||
|
FROM --platform=linux/amd64 alpine:3.22.7@sha256:dc92f017beea8e7f43263073d717f7bc011228c95d5312e440deb4d035bb4eec
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.title="WireGuard Admin Alpine integration fixture" \
|
||||||
|
org.opencontainers.image.version="3.22.7" \
|
||||||
|
org.opencontainers.image.base.name="docker.io/library/alpine:3.22.7" \
|
||||||
|
org.opencontainers.image.base.digest="sha256:dc92f017beea8e7f43263073d717f7bc011228c95d5312e440deb4d035bb4eec"
|
||||||
|
|
||||||
|
COPY packages.lock /tmp/alpine-packages.lock
|
||||||
|
RUN apk add --no-cache $(sed '/^#/d' /tmp/alpine-packages.lock) \
|
||||||
|
&& mkdir -p /usr/local/share/wireguard-admin \
|
||||||
|
&& install -m 0755 /tmp/alpine-packages.lock /usr/local/share/wireguard-admin/alpine-packages.lock \
|
||||||
|
&& rm /tmp/alpine-packages.lock
|
||||||
|
|
||||||
|
COPY runtime-check.sh /usr/local/bin/wireguard-admin-alpine-check
|
||||||
|
RUN chmod 0755 /usr/local/bin/wireguard-admin-alpine-check \
|
||||||
|
&& /usr/local/bin/wireguard-admin-alpine-check
|
||||||
|
|
||||||
|
CMD ["/bin/sh"]
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# Complete installed package closure from the selected-daemon Alpine 3.22.7
|
||||||
|
# linux/amd64 image build on 2026-10-22. Dockerfile treats every entry as an
|
||||||
|
# exact apk constraint. Rebuilds fail if this repository no longer serves the
|
||||||
|
# recorded versions rather than silently choosing a different closure.
|
||||||
|
alpine-baselayout=3.7.0-r0
|
||||||
|
alpine-baselayout-data=3.7.0-r0
|
||||||
|
alpine-keys=2.5-r0
|
||||||
|
alpine-release=3.22.7-r0
|
||||||
|
apk-tools=2.14.12-r0
|
||||||
|
bash=5.2.37-r0
|
||||||
|
busybox=1.37.0-r20
|
||||||
|
busybox-binsh=1.37.0-r20
|
||||||
|
ca-certificates-bundle=20261020-r0
|
||||||
|
iproute2=6.15.0-r0
|
||||||
|
iproute2-minimal=6.15.0-r0
|
||||||
|
iproute2-ss=6.15.0-r0
|
||||||
|
iproute2-tc=6.15.0-r0
|
||||||
|
libapk2=2.14.12-r0
|
||||||
|
libcap2=2.78-r0
|
||||||
|
libcrypto3=3.5.9-r0
|
||||||
|
libelf=0.193-r0
|
||||||
|
libmnl=1.0.5-r2
|
||||||
|
libncursesw=6.5_p20250503-r0
|
||||||
|
libssl3=3.5.9-r0
|
||||||
|
libxtables=1.8.11-r1
|
||||||
|
musl=1.2.5-r13
|
||||||
|
musl-utils=1.2.5-r13
|
||||||
|
ncurses-terminfo-base=6.5_p20250503-r0
|
||||||
|
openresolv=3.14.1-r0
|
||||||
|
readline=8.2.13-r1
|
||||||
|
scanelf=1.3.8-r1
|
||||||
|
ssl_client=1.37.0-r20
|
||||||
|
wireguard-tools=1.0.20250521-r0
|
||||||
|
wireguard-tools-wg=1.0.20250521-r0
|
||||||
|
wireguard-tools-wg-quick=1.0.20250521-r0
|
||||||
|
zlib=1.3.2-r1
|
||||||
|
zstd-libs=1.5.7-r0
|
||||||
Executable
+21
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
command -v wg >/dev/null
|
||||||
|
command -v ip >/dev/null
|
||||||
|
command -v busybox >/dev/null
|
||||||
|
wg --version >/dev/null
|
||||||
|
ip -Version >/dev/null
|
||||||
|
ip link show lo >/dev/null
|
||||||
|
|
||||||
|
if [ "${1:-}" = "--driver" ]; then
|
||||||
|
shift
|
||||||
|
[ "$#" -eq 1 ]
|
||||||
|
[ -x "$1" ]
|
||||||
|
# M8.4 provides this ignored cfg(test) entry point. Keep the selection
|
||||||
|
# exact so no other ignored test can run in the fixture image.
|
||||||
|
"$1" --ignored --exact container_driver::alpine_readiness --test-threads=1
|
||||||
|
elif [ "$#" -ne 0 ]; then
|
||||||
|
echo "usage: wireguard-admin-alpine-check [--driver PATH]" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,178 @@
|
|||||||
|
use std::{
|
||||||
|
io::Read,
|
||||||
|
process::{Command, Stdio},
|
||||||
|
thread,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
|
const COMMAND_DEADLINE: Duration = Duration::from_secs(15);
|
||||||
|
const OUTPUT_LIMIT: usize = 4096;
|
||||||
|
|
||||||
|
pub fn docker_text(arguments: &[&str]) -> Result<String, &'static str> {
|
||||||
|
docker_text_with_timeout(arguments, COMMAND_DEADLINE)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn docker_text_with_timeout(
|
||||||
|
arguments: &[&str],
|
||||||
|
command_deadline: Duration,
|
||||||
|
) -> Result<String, &'static str> {
|
||||||
|
docker_text_with_timeout_and_limits(arguments, command_deadline, OUTPUT_LIMIT, OUTPUT_LIMIT)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn docker_text_with_timeout_and_limits(
|
||||||
|
arguments: &[&str],
|
||||||
|
command_deadline: Duration,
|
||||||
|
stdout_limit: usize,
|
||||||
|
stderr_limit: usize,
|
||||||
|
) -> Result<String, &'static str> {
|
||||||
|
let mut child = Command::new("docker")
|
||||||
|
.args(arguments)
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.map_err(|_| "Docker CLI could not be started")?;
|
||||||
|
|
||||||
|
let stdout = child.stdout.take().ok_or("Docker stdout unavailable")?;
|
||||||
|
let stderr = child.stderr.take().ok_or("Docker stderr unavailable")?;
|
||||||
|
let stdout_reader = thread::spawn(move || read_capped(stdout, stdout_limit));
|
||||||
|
let stderr_reader = thread::spawn(move || read_capped(stderr, stderr_limit));
|
||||||
|
let deadline = Instant::now() + command_deadline;
|
||||||
|
|
||||||
|
let status = loop {
|
||||||
|
match child.try_wait().map_err(|_| "Docker command wait failed")? {
|
||||||
|
Some(status) => break status,
|
||||||
|
None if Instant::now() >= deadline => {
|
||||||
|
let _ = child.kill();
|
||||||
|
let _ = child.wait();
|
||||||
|
let _ = stdout_reader.join();
|
||||||
|
let _ = stderr_reader.join();
|
||||||
|
return Err("Docker command timed out");
|
||||||
|
}
|
||||||
|
None => thread::sleep(Duration::from_millis(20)),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (stdout, stdout_exceeded) = stdout_reader
|
||||||
|
.join()
|
||||||
|
.map_err(|_| "Docker stdout reader failed")?
|
||||||
|
.map_err(|_| "Docker stdout read failed")?;
|
||||||
|
let (stderr, stderr_exceeded) = stderr_reader
|
||||||
|
.join()
|
||||||
|
.map_err(|_| "Docker stderr reader failed")?
|
||||||
|
.map_err(|_| "Docker stderr read failed")?;
|
||||||
|
|
||||||
|
if !status.success() {
|
||||||
|
return Err(classify_docker_failure(status.code(), &stderr));
|
||||||
|
}
|
||||||
|
if stdout_exceeded || stderr_exceeded {
|
||||||
|
return Err("Docker command output exceeded its limit");
|
||||||
|
}
|
||||||
|
String::from_utf8(stdout).map_err(|_| "Docker returned non-UTF-8 output")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn classify_docker_error(stderr: &[u8]) -> &'static str {
|
||||||
|
let diagnostic = String::from_utf8_lossy(stderr).to_ascii_lowercase();
|
||||||
|
if diagnostic.contains("table") && diagnostic.contains("does not exist")
|
||||||
|
|| diagnostic.contains("no chain/target/match")
|
||||||
|
{
|
||||||
|
return "Docker host lacks required bridge networking support";
|
||||||
|
}
|
||||||
|
if diagnostic.contains("overlap") {
|
||||||
|
return "Docker rejected a test subnet because it overlaps an existing network";
|
||||||
|
}
|
||||||
|
if diagnostic.contains("subnet") || diagnostic.contains("ipv4") {
|
||||||
|
return "Docker rejected the IPv4 network parameters";
|
||||||
|
}
|
||||||
|
if diagnostic.contains("pool") || diagnostic.contains("address space") {
|
||||||
|
return "Docker rejected the test network address pool";
|
||||||
|
}
|
||||||
|
if diagnostic.contains("already exists") {
|
||||||
|
return "Docker reported a conflicting owned resource name";
|
||||||
|
}
|
||||||
|
if diagnostic.contains("no such container")
|
||||||
|
|| diagnostic.contains("no such network")
|
||||||
|
|| diagnostic.contains("no such object")
|
||||||
|
|| diagnostic.contains(" not found")
|
||||||
|
{
|
||||||
|
return "Docker named resource was not found";
|
||||||
|
}
|
||||||
|
if diagnostic.contains("permission denied")
|
||||||
|
|| diagnostic.contains("forbidden")
|
||||||
|
|| diagnostic.contains("operation not permitted")
|
||||||
|
{
|
||||||
|
return "Docker API denied the requested operation";
|
||||||
|
}
|
||||||
|
"Docker command returned a nonzero status"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn classify_docker_failure(status_code: Option<i32>, stderr: &[u8]) -> &'static str {
|
||||||
|
if matches!(status_code, Some(124 | 137 | 143)) {
|
||||||
|
return "In-container fixture command timed out";
|
||||||
|
}
|
||||||
|
classify_docker_error(stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_capped(mut reader: impl Read, limit: usize) -> std::io::Result<(Vec<u8>, bool)> {
|
||||||
|
let mut captured = Vec::new();
|
||||||
|
let mut buffer = [0_u8; 1024];
|
||||||
|
let mut exceeded = false;
|
||||||
|
loop {
|
||||||
|
let count = reader.read(&mut buffer)?;
|
||||||
|
if count == 0 {
|
||||||
|
return Ok((captured, exceeded));
|
||||||
|
}
|
||||||
|
let remaining = limit.saturating_sub(captured.len());
|
||||||
|
captured.extend_from_slice(&buffer[..count.min(remaining)]);
|
||||||
|
exceeded |= count > remaining;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
use super::{OUTPUT_LIMIT, classify_docker_error, classify_docker_failure, read_capped};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn output_capture_is_capped_while_the_pipe_is_drained() {
|
||||||
|
let input = vec![b'x'; OUTPUT_LIMIT + 17];
|
||||||
|
let (result, exceeded) =
|
||||||
|
read_capped(Cursor::new(input), OUTPUT_LIMIT).expect("read test bytes");
|
||||||
|
assert_eq!(result.len(), OUTPUT_LIMIT);
|
||||||
|
assert!(exceeded);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn docker_errors_are_reported_as_fixed_redacted_categories() {
|
||||||
|
assert_eq!(
|
||||||
|
classify_docker_error(b"iptables: Table does not exist; /etc/wireguard/private.conf"),
|
||||||
|
"Docker host lacks required bridge networking support"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
classify_docker_error(b"Pool overlaps with another subnet"),
|
||||||
|
"Docker rejected a test subnet because it overlaps an existing network"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
classify_docker_error(b"Error: No such object: wga-m83-net-test"),
|
||||||
|
"Docker named resource was not found"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
classify_docker_error(b"network wga-m83-net-test not found"),
|
||||||
|
"Docker named resource was not found"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn busybox_timeout_exit_status_is_classified_without_stderr() {
|
||||||
|
for status_code in [124, 137, 143] {
|
||||||
|
assert_eq!(
|
||||||
|
classify_docker_failure(Some(status_code), b""),
|
||||||
|
"In-container fixture command timed out"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
classify_docker_failure(Some(2), b""),
|
||||||
|
"Docker command returned a nonzero status"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
use std::{env, path::Path};
|
||||||
|
|
||||||
|
const OPT_IN: &str = "WIREGUARD_ADMIN_TESTCONTAINERS";
|
||||||
|
const SELECTED_ENDPOINT: &str = "WIREGUARD_ADMIN_TEST_DAEMON_ENDPOINT";
|
||||||
|
const SELECTED_ID: &str = "WIREGUARD_ADMIN_TEST_DAEMON_ID";
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Selection {
|
||||||
|
pub endpoint: String,
|
||||||
|
pub daemon_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Selection {
|
||||||
|
pub fn from_process_environment() -> Result<Self, &'static str> {
|
||||||
|
Self::from_values(
|
||||||
|
env::var(OPT_IN).ok().as_deref(),
|
||||||
|
env::var(SELECTED_ENDPOINT).ok().as_deref(),
|
||||||
|
env::var(SELECTED_ID).ok().as_deref(),
|
||||||
|
env::var("DOCKER_HOST").ok().as_deref(),
|
||||||
|
env::var("TESTCONTAINERS_COMMAND").ok().as_deref(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_values(
|
||||||
|
opt_in: Option<&str>,
|
||||||
|
endpoint: Option<&str>,
|
||||||
|
daemon_id: Option<&str>,
|
||||||
|
docker_host: Option<&str>,
|
||||||
|
testcontainers_command: Option<&str>,
|
||||||
|
) -> Result<Self, &'static str> {
|
||||||
|
if opt_in != Some("1") {
|
||||||
|
return Err("explicit opt-in is missing");
|
||||||
|
}
|
||||||
|
if testcontainers_command == Some("keep") {
|
||||||
|
return Err("Testcontainers keep mode is forbidden");
|
||||||
|
}
|
||||||
|
|
||||||
|
let endpoint = endpoint.ok_or("selected daemon endpoint is missing")?;
|
||||||
|
if !endpoint.starts_with("unix:///")
|
||||||
|
|| endpoint.contains(['\n', '\r', '\0'])
|
||||||
|
|| !Path::new(endpoint.trim_start_matches("unix://")).is_absolute()
|
||||||
|
{
|
||||||
|
return Err("only an explicitly selected local Unix socket is supported");
|
||||||
|
}
|
||||||
|
if docker_host != Some(endpoint) {
|
||||||
|
return Err("DOCKER_HOST must exactly match the selected endpoint");
|
||||||
|
}
|
||||||
|
|
||||||
|
let daemon_id = daemon_id.ok_or("selected daemon ID is missing")?;
|
||||||
|
if daemon_id.is_empty()
|
||||||
|
|| daemon_id.len() > 128
|
||||||
|
|| !daemon_id
|
||||||
|
.bytes()
|
||||||
|
.all(|byte| byte.is_ascii_alphanumeric() || byte == b'-')
|
||||||
|
{
|
||||||
|
return Err("selected daemon ID has invalid syntax");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
endpoint: endpoint.to_owned(),
|
||||||
|
daemon_id: daemon_id.to_owned(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Selection;
|
||||||
|
|
||||||
|
const ENDPOINT: &str = "unix:///var/run/docker.sock";
|
||||||
|
const ID: &str = "synthetic-daemon-id";
|
||||||
|
|
||||||
|
fn selection(
|
||||||
|
opt_in: Option<&str>,
|
||||||
|
endpoint: Option<&str>,
|
||||||
|
id: Option<&str>,
|
||||||
|
docker_host: Option<&str>,
|
||||||
|
keep: Option<&str>,
|
||||||
|
) -> Result<Selection, &'static str> {
|
||||||
|
Selection::from_values(opt_in, endpoint, id, docker_host, keep)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn requires_explicit_opt_in_endpoint_and_matching_current_daemon_identity() {
|
||||||
|
assert!(selection(None, Some(ENDPOINT), Some(ID), Some(ENDPOINT), None).is_err());
|
||||||
|
assert!(selection(Some("1"), None, Some(ID), Some(ENDPOINT), None).is_err());
|
||||||
|
assert!(selection(Some("1"), Some(ENDPOINT), None, Some(ENDPOINT), None).is_err());
|
||||||
|
assert!(
|
||||||
|
selection(
|
||||||
|
Some("1"),
|
||||||
|
Some(ENDPOINT),
|
||||||
|
Some(ID),
|
||||||
|
Some("tcp://remote:2376"),
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
selection(
|
||||||
|
Some("1"),
|
||||||
|
Some("tcp://remote:2376"),
|
||||||
|
Some(ID),
|
||||||
|
Some("tcp://remote:2376"),
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
selection(
|
||||||
|
Some("1"),
|
||||||
|
Some(ENDPOINT),
|
||||||
|
Some(ID),
|
||||||
|
Some(ENDPOINT),
|
||||||
|
Some("keep")
|
||||||
|
)
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
selection(Some("1"), Some(ENDPOINT), Some(ID), Some(ENDPOINT), None)
|
||||||
|
.expect("valid explicit local daemon")
|
||||||
|
.daemon_id,
|
||||||
|
ID
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
pub mod container_harness;
|
||||||
|
pub mod container_process;
|
||||||
|
pub mod container_selection;
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
mod support;
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use support::container_harness::{ProbeNames, cleanup_confirmed_probe_resources, run_probe};
|
||||||
|
use support::container_selection::Selection;
|
||||||
|
|
||||||
|
const CLEANUP_BUDGET: Duration = Duration::from_secs(40);
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore = "requires an explicitly selected disposable Docker daemon"]
|
||||||
|
fn selected_daemon_wireguard_and_application_smoke() {
|
||||||
|
let selection = Selection::from_process_environment()
|
||||||
|
.unwrap_or_else(|category| panic!("Testcontainers selection failed ({category})"));
|
||||||
|
let names = ProbeNames::unique();
|
||||||
|
let result = run_probe(&names);
|
||||||
|
|
||||||
|
// run_probe is synchronous: all Testcontainers requests return or reach the
|
||||||
|
// pinned client's request timeout before this ownership-checked cleanup.
|
||||||
|
let cleanup = cleanup_confirmed_probe_resources(
|
||||||
|
&selection,
|
||||||
|
&names,
|
||||||
|
&std::process::id().to_string(),
|
||||||
|
CLEANUP_BUDGET,
|
||||||
|
);
|
||||||
|
|
||||||
|
if !cleanup.is_empty() {
|
||||||
|
eprintln!(
|
||||||
|
"Testcontainers cleanup incomplete for owned resources {}, {}, {}, {} ({})",
|
||||||
|
names.network,
|
||||||
|
names.server,
|
||||||
|
names.baseline,
|
||||||
|
names.client,
|
||||||
|
cleanup.join(", ")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Err(category) = result {
|
||||||
|
panic!("Testcontainers probe failed ({category})");
|
||||||
|
}
|
||||||
|
if !cleanup.is_empty() {
|
||||||
|
panic!("Testcontainers probe passed but owned-resource cleanup was incomplete");
|
||||||
|
}
|
||||||
|
eprintln!(
|
||||||
|
"Testcontainers capability and Application smoke passed; owned resources: {}, {}, {}, {}",
|
||||||
|
names.network, names.server, names.baseline, names.client
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user