agent v0.85.0 WIP: F12/F11/F10/F9/F2/F1 boot-recovery plane + appliance self-heal (pre-build)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CDMFpFx84pfviCTVuGGhf
This commit is contained in:
2026-07-12 07:48:49 +02:00
parent bec4bac076
commit bc4eda926b
18 changed files with 1249 additions and 104 deletions
+125 -33
View File
@@ -9,23 +9,23 @@ import (
"testing"
)
// The §8 decision table, encoded exactly (RCA AUDIT-nas-cwa-rca-2026-07-11 fix 1).
func TestNetReassertAction_Table(t *testing.T) {
// The §8 decision table, encoded exactly (CAMPAIGN-3 F11: fstype-driven, automount state IGNORED).
func TestNetReassertClassify_Table(t *testing.T) {
cases := []struct {
fstype string
want string
}{
{"nfs4", NetReassertSkipActive}, // real mount — inherited by fresh namespaces
{"nfs", NetReassertSkipActive}, // real mount
{"cifs", NetReassertSkipActive}, // real mount
{"autofs", NetReassertRearmed}, // idle trigger — NOT inherited, re-arm to propagate
{"", NetReassertSkipNone}, // nothing at the path — removed/orphan, owned elsewhere
{"ext4", NetReassertSkipNone}, // a local fs at the path is not a network state we own
{"tmpfs", NetReassertSkipNone}, //
{"nfs4", NetReassertSkipActive}, // real mount — inherited by fresh namespaces
{"nfs", NetReassertSkipActive}, // real mount
{"cifs", NetReassertSkipActive}, // real mount
{"autofs", NetReassertRearmed}, // idle trigger — NOT inherited, re-arm to propagate
{"", NetReassertRearmed}, // F10: a failed/disarmed automount leaves NO mount entry — re-arm
{"ext4", NetReassertSkipForeign}, // a foreign local fs at the path is not ours to churn
{"tmpfs", NetReassertSkipForeign}, //
}
for _, c := range cases {
if got := netReassertAction(c.fstype); got != c.want {
t.Errorf("netReassertAction(%q) = %q, want %q", c.fstype, got, c.want)
if got := netReassertClassify(c.fstype); got != c.want {
t.Errorf("netReassertClassify(%q) = %q, want %q", c.fstype, got, c.want)
}
}
}
@@ -45,18 +45,26 @@ func installNetUnitFile(t *testing.T, unitDir string, spec NetworkMountSpec) (wh
return where, strings.TrimSuffix(mountUnit, ".mount") + ".automount"
}
func netReassertOps(t *testing.T, unitDir string, mounts []Mount) (*SudoHostOps, *recordingRunner) {
// netReassertOps builds ops with a hermetic unitFailed seam (default: nothing failed — no shelling out
// to a real systemctl is-failed). failedUnits, if set, marks specific unit names as failed.
func netReassertOps(t *testing.T, unitDir string, mounts []Mount, failedUnits ...string) (*SudoHostOps, *recordingRunner) {
t.Helper()
rr := &recordingRunner{}
ops := NewSudoHostOps(SudoHostOpsConfig{
Runner: rr, Bins: Binaries{}.withDefaults(), UnitDir: unitDir, StageDir: t.TempDir(),
Host: &fakeHostReader{mounts: mounts}, Logger: quietLogger(),
})
failed := map[string]bool{}
for _, u := range failedUnits {
failed[u] = true
}
ops.unitFailed = func(_ context.Context, unit string) bool { return failed[unit] }
return ops, rr
}
// An idle trigger (autofs at the mountpoint) must be re-armed with EXACTLY the granted verbs:
// `systemctl stop -- <unit>.automount` then `systemctl enable --now -- <unit>.automount`.
// `systemctl stop -- <unit>.automount` then `systemctl enable --now -- <unit>.automount`. No
// reset-failed when nothing is failed.
func TestReassertNetworkAutomounts_RearmsIdleTrigger(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("systemd-escaped unit filename contains a backslash; exercised on the Linux build server")
@@ -70,11 +78,8 @@ func TestReassertNetworkAutomounts_RearmsIdleTrigger(t *testing.T) {
if len(results) != 1 || results[0].Action != NetReassertRearmed || results[0].Err != nil {
t.Fatalf("want one rearmed result, got %+v", results)
}
if results[0].Where != where || results[0].Name != "media" {
t.Fatalf("result identity wrong: %+v", results[0])
}
if len(rr.calls) != 2 {
t.Fatalf("want exactly stop + enable --now, got %d calls: %v", len(rr.calls), rr.calls)
t.Fatalf("want exactly stop + enable --now (no reset-failed when clean), got %d calls: %v", len(rr.calls), rr.calls)
}
stop, enable := strings.Join(rr.calls[0], " "), strings.Join(rr.calls[1], " ")
if !strings.Contains(stop, "systemctl stop -- "+autoUnit) {
@@ -85,8 +90,44 @@ func TestReassertNetworkAutomounts_RearmsIdleTrigger(t *testing.T) {
}
}
// F10: a failed/start-limited automount (the campaign's unexport→idle-timeout→access×5 sequence leaves
// NO mount entry, fstype "") must be reset-failed FIRST, then re-armed — verdict reset-failed+rearmed.
// Companion to the campaign's reboots #2#4: the pre-0.85 code returned skip-none here and left it dead.
func TestReassertNetworkAutomounts_ResetsFailedThenRearms(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("systemd-escaped unit filename contains a backslash; exercised on the Linux build server")
}
unitDir := t.TempDir()
spec := NetworkMountSpec{Name: "media", Protocol: ProtocolNFS, Server: "10.0.0.5", Export: "/srv/media", MappedUID: 1000, MappedGID: 1000}
where, autoUnit := installNetUnitFile(t, unitDir, spec)
// Nothing mounted (fstype "" — a start-limited automount), and the automount unit is in failed state.
ops, rr := netReassertOps(t, unitDir, nil, autoUnit)
results := ops.ReassertNetworkAutomounts(context.Background())
if len(results) != 1 || results[0].Action != NetReassertResetRearmed || results[0].Err != nil {
t.Fatalf("want one reset-failed+rearmed result, got %+v", results)
}
// reset-failed <automount>, then stop + enable --now.
var sawReset, sawEnable bool
for _, c := range rr.calls {
j := strings.Join(c, " ")
if strings.Contains(j, "reset-failed -- "+autoUnit) {
sawReset = true
}
if strings.Contains(j, "enable --now -- "+autoUnit) {
sawEnable = true
}
}
if !sawReset {
t.Errorf("a failed unit must be reset-failed before re-arm (F10); calls: %v", rr.calls)
}
if !sawEnable {
t.Errorf("the trigger must still be re-armed after reset-failed; calls: %v", rr.calls)
}
}
// An ACTIVE real mount must not be touched — stopping the automount of a live mount would churn it.
// (Red-proof companion: a naive always-rearm implementation fails this with 2 recorded calls.)
// (Red-proof companion: a naive always-rearm implementation fails this with recorded calls.)
func TestReassertNetworkAutomounts_ActiveMountUntouched(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("systemd-escaped unit filename contains a backslash; exercised on the Linux build server")
@@ -105,27 +146,81 @@ func TestReassertNetworkAutomounts_ActiveMountUntouched(t *testing.T) {
}
}
// Neither a mount nor an armed trigger → skip (removed/orphan state, owned by add/remove flows).
func TestReassertNetworkAutomounts_NoTriggerSkips(t *testing.T) {
// The automount unit's own state is IGNORED (F11 red-proof): even though a mutant that consulted
// `systemctl is-active <automount>` would see an armed trigger as "active" and skip it, the fstype at
// the path is autofs (idle) so the correct code RE-ARMS. Encoded as: an idle trigger re-arms regardless
// of failed/armed unit state — the decision is fstype only.
func TestReassertNetworkAutomounts_IgnoresAutomountUnitState(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("systemd-escaped unit filename contains a backslash; exercised on the Linux build server")
}
unitDir := t.TempDir()
spec := NetworkMountSpec{Name: "media", Protocol: ProtocolNFS, Server: "10.0.0.5", Export: "/srv/media", MappedUID: 1000, MappedGID: 1000}
installNetUnitFile(t, unitDir, spec)
ops, rr := netReassertOps(t, unitDir, nil) // nothing at the mountpoint
where, _ := installNetUnitFile(t, unitDir, spec)
// fstype autofs (idle-armed): the correct decision is re-arm, NOT skip — a state-of-the-automount
// check would mis-skip an armed trigger (which always reports "active").
ops, _ := netReassertOps(t, unitDir, []Mount{{MountPoint: where, FSType: "autofs"}})
results := ops.ReassertNetworkAutomounts(context.Background())
if len(results) != 1 || results[0].Action != NetReassertSkipNone {
t.Fatalf("want one skip-none result, got %+v", results)
}
if len(rr.calls) != 0 {
t.Fatalf("a unit with no trigger must not be acted on, got: %v", rr.calls)
if len(results) != 1 || (results[0].Action != NetReassertRearmed && results[0].Action != NetReassertResetRearmed) {
t.Fatalf("an idle (autofs) trigger must re-arm regardless of automount unit state, got %+v", results)
}
}
// Idempotency: two consecutive passes over an idle trigger both succeed with the same action and no
// error (re-arming a fresh trigger is harmless — same end state).
// A foreign filesystem at the path (ext4/tmpfs) is skipped — re-arming over it would fail (busy).
func TestReassertNetworkAutomounts_ForeignFSSkipped(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("systemd-escaped unit filename contains a backslash; exercised on the Linux build server")
}
unitDir := t.TempDir()
spec := NetworkMountSpec{Name: "media", Protocol: ProtocolNFS, Server: "10.0.0.5", Export: "/srv/media", MappedUID: 1000, MappedGID: 1000}
where, _ := installNetUnitFile(t, unitDir, spec)
ops, rr := netReassertOps(t, unitDir, []Mount{{MountPoint: where, FSType: "ext4"}})
results := ops.ReassertNetworkAutomounts(context.Background())
if len(results) != 1 || results[0].Action != NetReassertSkipForeign {
t.Fatalf("want one skip-foreign result, got %+v", results)
}
if len(rr.calls) != 0 {
t.Fatalf("a foreign fs at the path must trigger zero systemctl calls, got: %v", rr.calls)
}
}
// F9: the pass returns exactly one verdict per installed unit — including a failed one. An
// empty-looking sweep over N shares is structurally impossible (the campaign's silent zero-line sweep).
func TestReassertNetworkAutomounts_VerdictPerUnit(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("systemd-escaped unit filename contains a backslash; exercised on the Linux build server")
}
unitDir := t.TempDir()
specs := []NetworkMountSpec{
{Name: "alpha", Protocol: ProtocolNFS, Server: "10.0.0.5", Export: "/srv/a", MappedUID: 1000, MappedGID: 1000},
{Name: "beta", Protocol: ProtocolNFS, Server: "10.0.0.6", Export: "/srv/b", MappedUID: 1000, MappedGID: 1000},
{Name: "gamma", Protocol: ProtocolNFS, Server: "10.0.0.7", Export: "/srv/c", MappedUID: 1000, MappedGID: 1000},
}
for _, s := range specs {
installNetUnitFile(t, unitDir, s)
}
// alpha active (skip-active), beta idle (rearm), gamma failed-and-unmounted (reset-failed+rearm).
gammaAuto := func() string {
u, _ := UnitNameForMount(specs[2].Where())
return strings.TrimSuffix(u, ".mount") + ".automount"
}()
ops, _ := netReassertOps(t, unitDir, []Mount{
{MountPoint: specs[0].Where(), FSType: "nfs4"},
{MountPoint: specs[1].Where(), FSType: "autofs"},
}, gammaAuto)
results := ops.ReassertNetworkAutomounts(context.Background())
if len(results) != len(specs) {
t.Fatalf("verdict count must equal unit count (%d), got %d: %+v", len(specs), len(results), results)
}
for _, r := range results {
if r.Action == "" {
t.Errorf("every share must carry a verdict (F9), got empty for %s", r.Name)
}
}
}
// Idempotency: two consecutive passes over an idle trigger both re-arm with the same action, no error.
func TestReassertNetworkAutomounts_Idempotent(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("systemd-escaped unit filename contains a backslash; exercised on the Linux build server")
@@ -140,9 +235,6 @@ func TestReassertNetworkAutomounts_Idempotent(t *testing.T) {
if first[0].Action != NetReassertRearmed || second[0].Action != NetReassertRearmed {
t.Fatalf("both passes must re-arm: first=%+v second=%+v", first, second)
}
if first[0].Err != nil || second[0].Err != nil {
t.Fatalf("idempotent passes must not error: first=%v second=%v", first[0].Err, second[0].Err)
}
if len(rr.calls) != 4 {
t.Fatalf("two passes = 2×(stop+enable), got %d: %v", len(rr.calls), rr.calls)
}