v0.117.0: consuming-namespace NAS verification + deploy-view truth (RCA fixes 2+4)
statfs fsclass helper (network/autofs/stub/unknown, fail-open); probe not_network_fs assertion (stub can never verify — red-proven); deploy-time stub refusal (idle autofs proceeds — red-proven); distinct stub badge, stub wins over unreachable (unreachable line byte-identical); deployed select shows stored HDD_PATH (red-proven vs IsDefault-only). MinAgent unchanged 0.81.0. Gates green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -5,6 +5,8 @@ import (
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/system"
|
||||
)
|
||||
|
||||
// In-guest uid-1000 write probe (NAS verify-before-commit, SPIKE-nas-verify Q2/Q3). The agent's
|
||||
@@ -20,21 +22,34 @@ const (
|
||||
netProbeExitNoWrite = 2 // create/write failed → not_writable (the squash trap)
|
||||
netProbeExitMismatch = 3 // readback failed or differed → probe_io
|
||||
netProbeExitCleanup = 4 // wrote fine but delete failed → OK + warn (never a failure)
|
||||
netProbeExitNotNetFS = 5 // dir is not a mounted network fs → not_network_fs (the stub trap, RCA fix 2)
|
||||
)
|
||||
|
||||
// netProbeReadBack is the child's readback seam (package var — the child is a re-exec'd process in
|
||||
// production, so a struct seam can't reach it; tests override in-process).
|
||||
var netProbeReadBack = os.ReadFile
|
||||
|
||||
// netProbeFSClass is the child's namespace-classification seam (RCA fix 2): the real value is
|
||||
// system.ClassifyPathFS (statfs f_type) on linux, a vacuous FSClassNetwork off-linux (the child
|
||||
// only ever runs in the linux container). Tests override to model a stub without a real mount.
|
||||
var netProbeFSClass = platformNetProbeFSClass
|
||||
|
||||
// NetProbeChild is the --netprobe body, run AS uid/gid 1000 by the re-exec parent: create a
|
||||
// dot-file with a random name + nonce in dir, read it back, compare, remove. Pure file logic —
|
||||
// unit-tested directly in t.TempDir(). Exposed for cmd/controller's hidden mode.
|
||||
// dot-file with a random name + nonce in dir — the create legitimately triggers the automount —
|
||||
// THEN require the dir to be a MOUNTED network filesystem (after a create, an autofs or local
|
||||
// answer means the mount did not materialize: the RCA's silent-stub trap), then read back,
|
||||
// compare, remove. Pure file logic — unit-tested directly in t.TempDir(). Exposed for
|
||||
// cmd/controller's hidden mode.
|
||||
func NetProbeChild(dir string) int {
|
||||
name := filepath.Join(dir, ".felhom-proba-"+randHexToken(8))
|
||||
nonce := randHexToken(32)
|
||||
if err := os.WriteFile(name, []byte(nonce), 0o644); err != nil {
|
||||
return netProbeExitNoWrite
|
||||
}
|
||||
if class := netProbeFSClass(dir); class != system.FSClassNetwork {
|
||||
_ = os.Remove(name) // best-effort — the verdict is already not-network-fs
|
||||
return netProbeExitNotNetFS
|
||||
}
|
||||
back, err := netProbeReadBack(name)
|
||||
if err != nil || string(back) != nonce {
|
||||
_ = os.Remove(name) // best-effort — the verdict is already mismatch
|
||||
@@ -65,6 +80,8 @@ func netProbeVerdict(exitCode int, output string) probeOutcome {
|
||||
return probeOutcome{OK: false, Category: "not_writable", Detail: "uid-1000 write probe: create/write refused | " + output}
|
||||
case netProbeExitMismatch:
|
||||
return probeOutcome{OK: false, Category: "probe_io", Detail: "uid-1000 write probe: readback failed or differed | " + output}
|
||||
case netProbeExitNotNetFS:
|
||||
return probeOutcome{OK: false, Category: "not_network_fs", Detail: "uid-1000 write probe: dir is not a mounted network filesystem in the controller namespace (stub) | " + output}
|
||||
default:
|
||||
return probeOutcome{OK: false, Category: "probe_io", Detail: "uid-1000 write probe: unexpected exit | " + output}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user