Merge pull request 'A host running as a service says what its apply did' (#49) from fix/a-host-running-as-a-service-says-what-it-did into main

This commit is contained in:
2026-09-29 07:16:01 +00:00
2 changed files with 51 additions and 1 deletions
+26 -1
View File
@@ -1246,6 +1246,21 @@ func applyDeclared(ctx context.Context, opts options, raw []byte, sched *apply.S
return applyAndKeep(ctx, opts, raw, nil, sched, say) return applyAndKeep(ctx, opts, raw, nil, sched, say)
} }
// announceOr is what the apply writes its detail with, given what the caller has to say things with.
//
// **Never nil.** This argument was nil on the serving path, and nil is silence: everything the apply
// says — a file held, a container replaced, the found firewall retired — was visible when a person ran
// the one-shot command and discarded in the way the host actually runs (novox/hq 04-ISSUES/143).
//
// Named rather than written inline at the call site so both paths reach the apply the same way, and so
// a reader asking "where does the apply's output go" finds one answer.
func announceOr(say link.Announce) func(string) {
if say == nil {
return func(string) {}
}
return say
}
// applying serialises applies within this process. // applying serialises applies within this process.
// //
// **Two things apply here: the link and the reconcile loop**, and each reads the node's state, // **Two things apply here: the link and the reconcile loop**, and each reads the node's state,
@@ -1308,8 +1323,18 @@ func applyAndKeep(ctx context.Context, opts options, raw []byte, signed *store.D
// Declared, not carried. A declaration from the mesh removes only what the mesh previously // Declared, not carried. A declaration from the mesh removes only what the mesh previously
// declared — never what this machine raised for itself from its bundle (04-ISSUES/010). // declared — never what this machine raised for itself from its bundle (04-ISSUES/010).
// **What the apply says goes to the console, which is the journal when this runs as a service.**
//
// It was nil, and nil is silence. The one-shot path has always passed a real one, so every detail
// the apply produces — a file held, a container replaced, the found firewall retired — was visible
// when a person ran it by hand and discarded in the way the host actually runs. Measured: after a
// machine was converged and its found firewall was not retired, what the host decided was
// unrecoverable, because it had said it to nobody (novox/hq 04-ISSUES/143).
//
// `say` already reaches stdout, and the launcher's unit sends that to the journal, so this needs
// no new mechanism — only for the argument to be passed.
outcome, updated, applyErr := apply.ApplyKeeping(ctx, built, declared, known, store.OriginDeclared, outcome, updated, applyErr := apply.ApplyKeeping(ctx, built, declared, known, store.OriginDeclared,
apply.ExecRunner, nil, sealOpener(opts.state), apply.KeepIn(filepath.Dir(opts.state))) apply.ExecRunner, announceOr(say), sealOpener(opts.state), apply.KeepIn(filepath.Dir(opts.state)))
// The mode the mesh said, recorded whichever way the apply went: the declaration is kept // The mode the mesh said, recorded whichever way the apply went: the declaration is kept
// either way, and the node is held to it from the next reconcile (novox/hq ADR 0100). // either way, and the node is held to it from the next reconcile (novox/hq ADR 0100).
+25
View File
@@ -568,3 +568,28 @@ func TestAConvergedMachineSaysItsOutwardLinksUnasked(t *testing.T) {
t.Fatal("a refused report is offered as news about the machine") t.Fatal("a refused report is offered as news about the machine")
} }
} }
// **A host running as a service says what its apply did.**
//
// The serving path passed nil where the apply writes its detail, and nil is silence. The one-shot path
// has always passed a real function, so everything the apply says was visible when a person ran it by
// hand and discarded in the way the host actually runs. Measured before this was written: a machine was
// converged, its found firewall was not retired, and what the host decided was unrecoverable because it
// had been said to nobody (novox/hq 04-ISSUES/143).
//
// This asserts only that the apply's log is never nil and that a line reaches what the caller gave.
// **It cannot catch the fault it was written for** — a call site passing nil directly — because that is
// wiring, and wiring is only proved by running the thing. That proof is a deployed host whose journal
// carries the apply's detail, which is how this fix was verified.
func TestTheApplysLogIsNeverNil(t *testing.T) {
if announceOr(nil) == nil {
t.Fatal("a host with nowhere to say things got a nil log, which the apply will call")
}
announceOr(nil)("this goes nowhere and must not panic")
var said []string
announceOr(func(line string) { said = append(said, line) })(" disabled ufw")
if len(said) != 1 || !strings.Contains(said[0], "disabled ufw") {
t.Fatalf("the apply's detail did not reach the caller's announce: %v", said)
}
}