116b1ea91a
Four defects from the follow-up audit. 1. Legacy migration swallowed every error. drop_legacy_managed_file was `let _ = file.remove(); Ok(())`, which tolerated not only the expected "that path is someone else's now" but also PermissionDenied, read errors and failed unlinks. If /etc/sysctl.d/99-awg2-harness.conf survived, it would keep winning over the new 90- file - it sorts later - while the reconcile reported success, so vm.swappiness would silently stay at the old value. The three cases are now distinguished: Absent is fine, Foreign warns and continues, Managed removes and any failure propagates. 2. The disable branches ran outside the lock. Both ensure_swap and ensure_sysctl_tuning checked `enabled` before acquiring their lock, so the cleanup path - which deletes managed files - was unsynchronised. A manual CLI invocation and the daemon's periodic reconcile could write and delete the same file concurrently. The lock is now taken first in both. 3. A failed teardown left foreign swaps half-disabled. disable_foreign_swaps aborted the loop on the first failure, so a host with two foreign swaps could end with the first one off and the second still up - a state nobody asked for and the error message did not describe. It now tracks what it disabled and re-enables it, in reverse order and with the original priority, before returning the error. The restore is best-effort and warns per entry rather than replacing the reported failure with a rollback failure. 4. Resizing dropped swap for the whole allocation. A live swapfile cannot be resized in place, and the previous code did swapoff, remove, fallocate/dd, mkswap, swapon. On a host with no other swap that is a swapless window spanning the slowest and most failure-prone part of the operation - on a machine that was given swap precisely because memory is tight, with no way back if any step failed. The replacement is now built and activated at a staging sibling while the old swap is still serving, and only then is the old file released. The remaining gap is swapoff/rename/swapon, which allocates nothing. A failure while staging removes the staging file and leaves the original swap untouched; a failure at the final rename says explicitly that the file is in place and can be activated with swapon. Related, same function: changing only swap.priority used to fall through to the full recreate path, destroying and rebuilding a correct multi-gigabyte file to change one integer. Size and priority are now separate decisions - a priority-only change is a reactivation. The reconciliation is now decided by an explicit match over (size correct, currently active) rather than a single `geometry_ok` flag, which is what hid the priority case. Three tests added around the new decision surface: the priority-only case is not a resize, the staging path is a sibling of the swapfile, and the managed swap is never part of the foreign teardown set. cargo test --workspace: 179 + 4 passed. clippy -D warnings clean.