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.
59 lines
3.0 KiB
Markdown
59 lines
3.0 KiB
Markdown
---
|
|
status: resolved
|
|
opened: 2026-09-27
|
|
located-in: [mesh-controller cmd/mesh-controller/push.go, mesh-controller cmd/mesh-controller/sendable.go]
|
|
fixed-by: 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`, and `push` says *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.
|
|
|