mesh/merge-gate pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
mesh/delivery-group group fix/361-the-setuid-search-runs-to-completion delivered: every member is delivered
The review found a cursor of a name that is not UTF-8 came back changed from JSON, so a resumed walk skipped what it stood before; an ask to search again was lost to a restart, which carried the older walk on; a fresh search judged a filesystem it never walked; a mount point root cannot read failed or hung the walk; a found file gone before the package manager was asked failed every resume; and skipped or kept outcomes started a search on every reconcile, back to back with a long walk.
50 lines
1.8 KiB
Go
50 lines
1.8 KiB
Go
package apply
|
|
|
|
import (
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/novox/mesh-host/internal/declaration"
|
|
)
|
|
|
|
// rootPaths are the files and directories through which an account comes to root, or by which it is told who it
|
|
// is: the user and group databases, sudo's, doas's and polkit's rules. A file written there changes what the
|
|
// judge of an agent account reads (novox/hq ADR 0266).
|
|
var rootPaths = []string{"/etc/passwd", "/etc/shadow", "/etc/group", "/etc/gshadow", "/etc/sudoers",
|
|
"/etc/sudoers.d", "/etc/doas.conf", "/etc/doas.d", "/etc/polkit-1", "/usr/share/polkit-1"}
|
|
|
|
// MovesRoot says an apply changed something through which root's reach could have changed — an account, a
|
|
// package, an action run as root, or a file in rootPaths (novox/hq issue 361). The engine then asks for a new
|
|
// search for setuid programs: root acted, and a complete search is sound only until root acts. Outcomes that
|
|
// moved nothing (unchanged, held, waiting, held still, skipped, kept, forgotten) do not count.
|
|
func MovesRoot(outcomes []Outcome) bool {
|
|
for _, o := range outcomes {
|
|
// Only what moved the machine: skipped, kept and forgotten touch nothing, and counting them made one
|
|
// broken module start a search on every reconcile (the review of 2026-10-10).
|
|
switch o.Action {
|
|
case "created", "updated", "corrected", "removed":
|
|
default:
|
|
continue
|
|
}
|
|
switch declaration.Type(o.Type) {
|
|
case declaration.TypeUser, declaration.TypePackage, declaration.TypeAction:
|
|
return true
|
|
case declaration.TypeFile, declaration.TypeDirectory, declaration.TypeArchive:
|
|
if underRootPath(o.Target) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func underRootPath(target string) bool {
|
|
t := filepath.Clean(target)
|
|
for _, p := range rootPaths {
|
|
if t == p || strings.HasPrefix(t, p+"/") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|