Repository navigation
Closed
Labels
Description
Context
Audit of the modules on 2026-11-09 against the AGENTS.md rules "file writes go through rc.Runner", "Check and Apply must agree" and "tests must not touch the host".
- verified:
ensureMetaDir(internal/module/module.go:240-262) migrates a pre-v0.1 single-file layout withos.Rename/os.MkdirAlland runs during--dry-run. The layout dates from the initial commit; 18 releases have shipped since. - inferred (agent read, re-check on implementation):
saveGPUDB(gpu.go:782-786) writes withos.WriteFile+os.Rename(no dry-run, no backup);access.go:92anddoctor.go:129os.MkdirAll("/run/sshd")inside read-only paths;users.go:1123writes the users DB non-atomically. - inferred:
security.go:241-251reportsChangedfor time sync even when both commands only warned; Ubuntu Check hides atimedatectlerror (security.go:157); severalChange.Commandstrings hard-codeapt-get installon paths that also run on Rocky (docker.go:43,nvidia.go:33,security.go:146,monitoring_exporters.go:49,network.go:46); locale Check showsupdate-localewhile Apply writes the file. - inferred: tests with a dry-run Runner still execute real
dpkg-query,ufw status,sshd -t,systemctland read/etc(Runner.Queryis not gated):ssh_test.go:62,docker_test.go:19,31,nvidiatests, GPU/storage tests vianewDryRunRC. Most assert only non-nil.
Implementation starts only on the owner's go-ahead.
Decisions
- Remove the legacy
.rootfilessingle-file migration; a file at that path becomes an error that says how to move it. - JSON databases (users, GPU) share one atomic read/write helper built on
rc.Runner(same pattern as tenantwriteIfChanged). - Module tests stub commands with
fakeBinand path variables; a test that cannot be made host-free is deleted. - 2026-11-09: re-verified by the implementer against the fix(firewall): block SSH and UFW changes when firewall state is unknown #89 head.
saveGPUDBalready skips writes in dry-run; the gap is that its real write/rename bypasses Runner backups.saveUserMetaalready usesRunner.WriteFilebut is not atomic. TheensureMetaDirmigration, the/run/sshdmkdir in access/doctor, and the security time-sync Changed/error handling are confirmed. Basis: implementer source check.
Design
internal/module/jsondb.go:readJSONDB(path, v) (found bool, err),writeJSONDB(rc, path, v)viaRunner.WriteFile(tmp)+Runner.Rename. Missing file means empty DB for both callers.- Check-only paths stop creating
/run/sshd; runsshd -twith a temp-o PidFile/privsep check or report "cannot validate" instead. Change.CommandusespackageInstallCommand.- A test guard:
Runner.Queryin tests fails when the binary resolves outside the fakeBin dir (opt-in helper used bynewDryRunRC).
Changes
internal/module/{module,gpu,users,access,doctor,security,docker,nvidia,monitoring_exporters,network,locale}.go, newjsondb.go, the listed tests,internal/exec/runner.go(test hook only).
Acceptance criteria
- AC1:
apply --dry-runmakes no filesystem change (test with a temp home base containing the legacy file: still a file afterwards, error message names it). - AC2: no
os.WriteFile/os.Rename/os.MkdirAll/os.Removeininternal/moduleoutside tests (grepcheck in a test or CI step). - AC3: Check output equals the commands Apply runs for docker/nvidia/security/network/locale on Rocky and Ubuntu stubs.
- AC4:
go test ./internal/module/...passes on a machine withoutufw,dpkg-query,sshd,systemctl(CI macOS or a stripped container) with no host reads under/etc.
Tasks
- T1: remove legacy migration (AC1)
- T2:
jsondb.go, switch GPU/users DB (AC2) - T3: read-only paths stop writing (AC2)
- T4: Check/Apply command agreement, Changed accuracy (AC3)
- T5: host-free tests (AC4)
Out of scope
- Extracting an
exec.Runnerinterface (decided against in the 2026-09 improvement plan; fakeBin stays).
Activity