From bb334e138b0b1f73db4cab290a34029a2251fa17 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 26 Sep 2026 22:24:58 +0200 Subject: [PATCH] issue 127: a declaration that shrinks to empty is skipped, so the node keeps what it should drop --- .../00-report.md | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 04-ISSUES/127-a-declaration-that-shrinks-to-empty-is-skipped-not-sent/00-report.md diff --git a/04-ISSUES/127-a-declaration-that-shrinks-to-empty-is-skipped-not-sent/00-report.md b/04-ISSUES/127-a-declaration-that-shrinks-to-empty-is-skipped-not-sent/00-report.md new file mode 100644 index 0000000..9a57cb6 --- /dev/null +++ b/04-ISSUES/127-a-declaration-that-shrinks-to-empty-is-skipped-not-sent/00-report.md @@ -0,0 +1,39 @@ +--- +status: located +opened: 2026-09-27 +located-in: [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.