D65 = A: the busy file the agent reads before an operator reboot, and the household's 'Felhom support restarted your box' line; R-1350: a hold clear records the wanted stop so no app_start_failed follows it
gates / gates (push) Successful in 32s

This commit is contained in:
admin committed 2026-10-29 16:24:40 +01:00
1 parent a28db2d17f
commit b69e90bdad
22 files changed
+662 -11

No files matched your search

@@ -160,6 +160,26 @@ func isObservedUp(s Stack) bool {
}
}
// RecordStopWantedIfDown (R-1350) is the operator's hold clear (D64 = B) speaking for the household: the clear starts
// nothing, so an app that is NOT up stays stopped — and that stop is now WANTED, recorded as „stopped" so the dead-app
// scan does not mail „Telepített alkalmazás nem fut" 30 s after the clear (measured 2026-10-29 on Tester 1), and the
// boot reconciler does not start it at the next boot. A later Start records „running" and ends it. A running app's wish
// is left alone (recorded=false). The clear is a PERSON's act (R-379: „a person — on the box, or the operator on the
// hub"), so this is the ownership rule's own exception, not a machine writing intent.
func (m *Manager) RecordStopWantedIfDown(name string) (bool, error) {
st, ok := m.GetStack(name)
if !ok {
return false, nil
}
if isObservedUp(*st) {
return false, nil
}
if err := m.SetDesiredState(name, DesiredStateStopped); err != nil {
return false, err
}
return true, nil
}
// WantsStopped (R-721, v0.283.0) reports whether the household's own recorded intent for the stack is
// „stopped". The machines that stop an app for their own purposes and start it again afterwards — the
// whole-guest backup's quiesce, the nightly volume dump — ask this before their restart, so a Stop the
@@ -336,3 +336,27 @@ func TestDesiredStateOf_NilAppConfigIsUnknown(t *testing.T) {
t.Fatalf("a stack with no AppConfig reported desired state %q — absent must read as unknown", got)
}
}
// R-1350: the operator's hold clear records „stopped" for an app that is NOT up, and leaves a running app's wish alone
// (a clear never stops anything). RED-PROOF: write „stopped" without the up check → the running case FAILS.
func TestR1350_RecordStopWantedIfDown(t *testing.T) {
m, root := newDSManager(t, "gokapi", "immich")
for _, n := range []string{"gokapi", "immich"} {
writeAppYAML(t, stackDirOf(root, n), "deployed: true\ndesired_state: running\n")
m.stacks[n].AppConfig = LoadAppConfig(stackDirOf(root, n))
}
m.stacks["gokapi"].State = StateStopped
m.stacks["immich"].State = StateRunning
if rec, err := m.RecordStopWantedIfDown("gokapi"); err != nil || !rec {
t.Fatalf("gokapi: recorded=%v err=%v", rec, err)
}
if got := LoadAppConfig(stackDirOf(root, "gokapi")).DesiredState; got != DesiredStateStopped {
t.Fatalf("gokapi desired_state on disk = %q, want stopped", got)
}
if rec, err := m.RecordStopWantedIfDown("immich"); err != nil || rec {
t.Fatalf("immich (running): recorded=%v err=%v", rec, err)
}
if got := LoadAppConfig(stackDirOf(root, "immich")).DesiredState; got != DesiredStateRunning {
t.Fatalf("a running app's wish was changed to %q", got)
}
}