A bare alive moves last_seen and nothing else (convergence race fix) #7

Merged
jschoubben merged 1 commits from fix/service-only-converge into main 2026-09-05 20:47:23 +00:00
Owner

Fixes a latent race: a bare alive heartbeat built an empty report whose SQL overwrote node_report.declared with '', and since current requires declared == sent, any heartbeat after a slow apply flipped the node to current:false and it never recovered. A fast apply (catalogue-small) beat the 60s alive tick, so it never surfaced there; a slow apply (heavy modules) is clobbered before settled() can see current:true. 12-line fix — a bare alive touches only last_seen, exactly as commit f0cff88's own message intended — plus a regression test that fails without it.

Lab-verified: the four-module apps wave (mongodb, unifi, marrytts, postgres) now reaches current:true and passes (SUITE_EXIT=0), where it previously stalled to the settle timeout. Would have bitten any slow multi-module install.

https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF

Fixes a latent race: a bare `alive` heartbeat built an empty report whose SQL overwrote `node_report.declared` with `''`, and since `current` requires `declared == sent`, any heartbeat after a slow apply flipped the node to `current:false` and it never recovered. A fast apply (catalogue-small) beat the 60s alive tick, so it never surfaced there; a slow apply (heavy modules) is clobbered before `settled()` can see `current:true`. 12-line fix — a bare alive touches only `last_seen`, exactly as commit f0cff88's own message intended — plus a regression test that fails without it. **Lab-verified:** the four-module apps wave (mongodb, unifi, marrytts, postgres) now reaches `current:true` and passes (SUITE_EXIT=0), where it previously stalled to the settle timeout. Would have bitten any slow multi-module install. https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
jschoubben added 1 commit 2026-09-05 20:47:17 +00:00
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
jschoubben merged commit bcdde475cd into main 2026-09-05 20:47:23 +00:00
jschoubben deleted branch fix/service-only-converge 2026-09-05 20:47:23 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: novox/mesh-controller#7