SmartConfig

Review of M3 chunk D (steps 12-14)

Date: 2026-10-03. One reviewer (an AI agent), on c6cef99..a66625c: scd’s checker goroutine (step 12), the netplan, udev, passwd, group and sysctl checkers (step 13), sc scope (step 14), sc check --as and scripts/accept-m3.sh. The reviewer read the code and did not run it. Claude reproduced or tested each finding on this VM before fixing it.

Findings

Area Finding Severity Fixed in
passwd passwd-root was a blocker (“sudo and su fail”) for a file with no root line, or one glibc cannot read. With passwd: files systemd in nsswitch, as on stock Ubuntu, nss-systemd supplies root and sudo keeps working. Reproduced on glibc 2.39 by bind-mounting a passwd file with no root line over /etc/passwd in a private mount namespace: getent passwd root gives uid 0 and sudo -n id -u prints 0. The acceptance script expected the blocker. high 2fb037e: a warning when nsswitch lists systemd for passwd. A root line whose uid is not 0 is still a blocker.
netplan When another netplan file could not be read (a user running sc check, a 0600 50-cloud-init.yaml), it was left out of the merge with a note, but an error caused by its absence (an interface “not defined”) was still a blocker in this file. high 2fb037e: with the merge incomplete, an error that may come from a missing file (an interface not defined, already assigned, a device type change, a definition or writer error) is a note asking for root. Errors in the file’s own lines are still findings.
scd checker A failed check of the version before stopped the whole check, so the new version was not checked; and on shutdown it logged a spurious error. medium 2fb037e: the failure is logged once at notice and every finding of the new version is reported; a cancelled context ends quietly.
--as sc check --as PATH FIFO opened the FIFO before looking at it and waited for a writer forever. Reproduced. medium 2fb037e: the candidate is opened with O_NONBLOCK and then refused as not a regular file.
sc scope It treated every login’s .ssh as a root without the lstat check scd does, so it said “recorded: yes” for a path under a symlinked or missing .ssh that scd never watches. medium 2fb037e: every root is looked at as scd does. Under a missing, symlinked or non-directory root a path is “recorded: no, scd does not watch …”. A root sc may not look at (/root/.ssh for a user) gives “yes if scd watches …; run sc scope as root”.
scd checker Each change validated the version before again, though the last check had just validated it as its “after”. low 2fb037e: the findings of the last check are kept per path and blob and reused. Bounded like scd’s other per-path maps.
sysctl The reviewer read the line matching to say key = and = 1 are invalid syntax for procps, so a later message could land on the wrong line. low Not reproduced. procps 4.0.4 gives one cannot stat message per key = line, in order, and Path is not under /proc/sys for = 1. sc puts each on its own line (checked with sc check --as). No change.
code Explain repeated decide’s walk and the tier and digest scans, so sc scope could drift from scd. cleanup 2fb037e: decide returns the deciding line; Recorded and Explain both use it, and Tier and FingerprintOnly share one lookup.
code Two lists of no-login shells (scope, check) and two PathError helpers (watch, check). cleanup 2fb037e: scope.NoLogin and fsutil.ErrText.
code sc check built a second, throwaway Checks just to read its graph, calling the test hook twice. cleanup 2fb037e: one Checks per run, and Checks.GraphInUse replaces four copies of “the graph, or the default”.

Found while fixing

A go build ./cmd/sc at the repo root leaves an sc binary there, and a git add -A took it into the fix commit before it was pushed. It was taken out with an amend, and /sc is now in .gitignore.

Tests

TestPasswdRoot (with nss-systemd: no root line, an unreadable root line, a non-zero uid; without it, or with it only for group: blockers), TestNetplanUnreadableMerge (five errors that become a note, one that stays a blocker; disabling the rule fails all five), TestCheckRunsOnce (two validator runs at the first check, one at the next), TestCheckCLIAs (a FIFO is refused within 10 s; with the old open it times out), TestScopeUnwatchedRoots (symlink, missing, a file, and a home sc may not enter), TestCheckCLIOneChecks. TestScopeCLI no longer depends on the machine’s /home. All tests pass as a user, as root and under race.