1.9 KiB
status, opened, located-in
| status | opened | located-in | |
|---|---|---|---|
| located | 2026-09-27 |
|
A declaration that shrinks to empty is skipped, so the node keeps what it should drop
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.