A number identifies a record, and two were given 127 on 2026-09-27. The one three documents and three source files cite by number keeps it; the other becomes 149, says so in its own heading, and its two inbound references are repointed. It is also resolved: an empty declaration is sent carrying owns_nothing rather than skipped, and the host refuses an empty body that does not carry it, so emptiness cannot be read as a truncated declaration.
3.0 KiB
status, opened, located-in, fixed-by
| status | opened | located-in | fixed-by | ||
|---|---|---|---|---|---|
| resolved | 2026-09-27 |
|
mesh-controller sendable.go and push.go — an empty declaration is sent carrying `owns_nothing`, and the host refuses an empty body that does not carry it |
149 — a declaration that shrinks to empty is skipped, so the node keeps what it should drop
Opened as 127 and renumbered on 2026-09-29: two records were given that number on the same day. The other kept it, because three documents and three source files cite it by number and nothing cited this one but a decision and a sibling issue, both corrected with this move.
What was observed
Fixing the broker-opening leak (the foundation port scoped to the broker's host) made ace's
declaration compose to zero resources — ace is adopted with nothing assigned, and the
stray opening was its only resource. push ace then printed ace is assigned nothing — skipped and sent nothing. ace goes on holding adoption.opening-tcp-5671-incoming in its
ufw, because it was never told the resource is gone.
composeEach (push.go) skips any node whose composed declaration has no resources. That is
right for a node that never had anything. It is wrong for a node that had resources and
now composes to none: the empty declaration is the correction, and skipping it leaves the last
non-empty one in force forever.
Why it matters
Any adopted node whose openings (or other baseline resources) are all removed keeps the stale ones until something else pushes a non-empty declaration to it. Converge is unaffected — it composes the full ruleset fresh — so this is an incremental-push gap, not a firewall-safety one. But "the mesh cannot tell a node to drop its last resource" is a real hole in reconcile.
The fix, roughly
Send the empty declaration when the node's last-sent declaration was non-empty — i.e. skip only when empty-and-was-already-empty. Requires push to know (or the host to be told) that the node held something. Simplest: always send to a placed, enrolled node; let an empty declaration mean "own nothing", which the host already applies correctly when it receives one.
Workaround used
On ace, one command drops it permanently (the corrected controller never re-composes it):
sudo ufw delete allow 5671. At ace's converge it would clear on its own.
Closed
2026-09-29, in a grooming pass. Both halves are on main and both name this issue.
- The control plane sends it: a declaration that composes to no resources goes out with
owns_nothing, andpushsays sent, not skipped. - The host refuses an empty body that does not carry it, so a truncated or mis-composed declaration can never be read as "own nothing" — which is the failure the fix had to avoid while making the empty case expressible.
Closed by reading the code rather than by watching a machine let go of a stray resource; the record says so rather than implying a run.