Files
hq/04-ISSUES/127-a-declaration-that-shrinks-to-empty-is-skipped-not-sent/00-report.md
T

1.9 KiB

status, opened, located-in
status opened located-in
located 2026-09-27
mesh-controller cmd/mesh-controller/push.go

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.