A bare alive moves last_seen and nothing else

A node says it is there every minute and describes what it applied rarely,
and both went through Heard, which wrote every one down as a report. So a
bare alive replaced the node's last real apply with an empty one -- clearing
the declaration digest `current` is measured against, the carried ports a
push assigns around, and the clean-or-failed outcome. A node that had just
caught up read as behind within the minute, and never converged.

Whether it converged in time was a race the node's own apply set: the link's
one loop applies a declaration to completion before it can send the pending
heartbeat, so a fast apply (catalogue-small) leaves the digest standing the
~60s until the next beat -- long enough for the lab to see `current` -- while
a heavy wave whose apply outran the first beat (mongodb + unifi + marrytts)
had the alive fire milliseconds after the report and never showed `current`
at all, timing out settle even at 1200s.

Heard now returns after moving last_seen for a report that carries no account
of what the machine did -- nothing applied, nothing refused, nothing failed,
which is exactly a bare alive. A real report always carries one. This is what
the commit that began hearing alives said it did and did not: "a bare word
that a node is there moves last_seen and touches nothing else."

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
This commit is contained in:
2026-09-05 22:37:53 +02:00
parent 59fcb41855
commit cae9a3e54f
2 changed files with 75 additions and 0 deletions
+12
View File
@@ -151,6 +151,18 @@ func (e Enrolment) Heard(ctx context.Context, report Report) error {
if err != nil {
return err
}
// A bare word that a node is there is not an account of what the machine did or holds: it
// moves last_seen and touches nothing else. This arrives every minute (link.AliveEvery),
// while a real report is rare, so recording it as one would overwrite the node's last real
// apply with an empty one — wiping the declaration digest that decides whether the node is
// current, the carried ports a push assigns around, and the clean-or-failed outcome — and a
// node that had just caught up would read as behind within the minute. The alive path calls
// this with only a node name; a real report always carries an account (something applied, or
// a refusal, or a failure), so those are the reports that get written down.
if report.Applied == nil && report.Refused == "" && len(report.Failed) == 0 {
return e.Inventory.Seen(ctx, node.ID)
}
// What it did is kept whichever way it went. Until this, a refusal or a failure moved
// last_seen and the reason went to a log line, so "which machine is not doing what it was
// told" had no answer the next morning — which is the question a mesh exists to answer.