Files
hq/04-ISSUES/130-undeclaring-a-service-stops-it/00-report.md
T
jschoubben 14be8576f8 Grooming: five issues were fixed and never closed, and one is not
088, 089, 120, 128 and 130 each name a commit that is on main and cites them —
the forge's address following a moved port, a route naming its endpoint, a
provisioner asking the backend what is there, the hosts file written into a
marked block, and undeclaring giving a unit back the state it was found in.
Each says it was closed by reading commits rather than by a run, so nobody
reads a green that was never measured.

129 stays located on purpose: ca-trust is merged and no machine holds it, so
the symptom it opened on is still true everywhere.
2026-09-29 22:25:25 +02:00

4.3 KiB

status, fixed-by, opened, located-in, amended-design
status fixed-by opened located-in amended-design
resolved mesh-host 3112c88 — undeclaring gives a unit back the state it was found in, and removes only a process the mesh made 2026-09-27
mesh-host internal/apply/apply.go (remove)
02-DECISIONS/0118-undeclaring-gives-a-unit-back-the-state-it-was-found-in.md

130 — undeclaring a service stops it, even one the mesh only reloads or only keeps running

What was observed

Reviewing the uplink modules (ADR 0125) found that the host's remove path stops every service resource that is no longer declared: SetServiceState(..., "stopped"), reported as "stopped; the unit file is not the host's to delete". store.Orphans matches by id alone. So any of these stops the unit:

  • the module is unassigned — by mistake, or to switch it for another;
  • the node is sent a deliberately-empty declaration (issue 127);
  • a later catalogue version renames the resource's id.

That is right for a service the mesh brought into being. It is wrong for a unit the mesh declares only to act on — and the catalogue already has two:

  • The private network declares docker.service (registry-trust-reload, state running) so that a change to the registry trust reloads the runtime (ADR 0102). Unassigning the private network stops the container runtime, and every container on the machine with it — including ones the mesh does not manage.
  • The sshd module declares sshd.service. Unassigning it stops the machine's ssh daemon: the lockout the same module's listens rule says a firewall must never arrange.

The uplink modules would have added a third and a fourth: unassigning the network manager's module would have stopped the network manager, taking the machine off the only link the mesh reaches it by.

What would have prevented it

  • A service resource that says the unit's lifecycle is the machine's: declared with no state, the mesh never starts, stops, enables or disables it; it only reloads or restarts a running unit when a trigger changes; undeclared, it is left exactly as it is. (Being built on mesh-host feat/a-file-written-into-a-marked-block for the uplink modules.)
  • Then: registry-trust-reload declared that way (the runtime is the machine's), and the sshd module's service too — a machine's ssh daemon outlives any module that configures it.
  • A plan or unassign preview that names every unit an undeclare will stop, so the consequence is read before it happens.

Resolution

ADR 0126: undeclaring removes what the mesh made and gives back what it changed. The host records the state it first found a unit in, and undeclaring returns the unit to it — a unit found running (the container runtime, sshd, a network manager) is left running; one the mesh started (the packet filter a converge loaded) is stopped again; nothing is started on the way out; a record from before the host kept what it found leaves the unit alone. That covers the runtime, sshd and the uplink modules at once, without each module opting out; the private network and the sshd module need no change.

A first draft — never stop a unit the mesh did not create — was rejected while implementing it: returning a converged node to adopted unloads the mesh's filter by exactly this path.

Found on the way: an undeclared process failed every apply on its node (remove had no case for it). Now removed with its unit, timer and bundle — the mesh's own code. user and archive have the same gap and are left for their own decisions: removing a login or unpacked files is not something to settle in passing.

The unassign preview is partly answered — the host's plan names each unit it will stop — and the controller's side is left open.

Closed

2026-09-29, in a grooming pass rather than by whoever fixed it. Undeclaring no longer stops a unit the mesh only reloaded or only kept running. Found by reading what the code repositories' commits cite: the fix names this issue and is on main. It was not re-verified on a machine, and this record says so rather than implying a run that did not happen.