ADR 0118: undeclaring removes what the mesh made, gives back what it changed, leaves the machine's units as they are — resolves issue 130

This commit is contained in:
jochen
2026-09-27 00:58:17 +02:00
parent dd4cbabffb
commit ebd19c4c6c
3 changed files with 113 additions and 1 deletions
@@ -0,0 +1,100 @@
---
topic: what runs on it
status: accepted
date: 2026-09-27
deciders: jochen
reconstructed: false
extends: 0102-the-mesh-writes-into-a-shared-file-never-over-it.md
---
# 118. Undeclaring removes what the mesh made, gives back what it changed, and leaves the machine's units as they are
## Context
When a resource stops being declared — its module unassigned, the node sent a
deliberately-empty declaration ([issue 127](../04-ISSUES/127-a-declaration-that-shrinks-to-empty-is-skipped-not-sent/00-report.md)),
or a new catalogue version renaming its id — the host undoes it. The host's own code states
the rule it means to follow: **it removes what it made and leaves what it merely configured.**
For almost every resource it does exactly that:
- a container, a network, a process's unit, a directory it created: removed;
- a file it created: removed; a file it replaced: its kept original put back
([ADR 0100](0100-a-node-in-use-is-adopted-before-it-is-converged.md));
- keys and list members it wrote into a shared file: given back as they were
([ADR 0102](0102-the-mesh-writes-into-a-shared-file-never-over-it.md));
- a package: left installed — the host cannot know it is unused;
- an operator's path it was given access to: never touched
([ADR 0051](0051-shared-data-is-the-operators.md)).
**A service is the exception.** A `service` resource never installs a unit: it puts one that
already exists — the distribution's, the operator's — into a state. Undeclared, the host stops
it. That contradicts the rule above, and in practice it is the most dangerous thing an
undeclare can do. Found reviewing the uplink modules
([issue 130](../04-ISSUES/130-undeclaring-a-service-stops-it/00-report.md)):
- the private network declares the container runtime's unit only so a change to the registry
trust reloads it — unassigning the private network stops the runtime, and every container on
the machine, the mesh's and not;
- the sshd module declares the ssh daemon — unassigning it stops ssh, the lockout that module's
own `listens` rule forbids;
- the uplink modules would have stopped the network manager, taking the machine off the only
link the mesh reaches it by.
ADR 0117 (in review) answered that for its own modules with a
service declared with no `state`. Every other module that declares a unit it did not make is
exposed in the same way, and relying on each author to remember an opt-out is how the next one
is missed.
## Considered Options
**1. Undeclaring touches nothing on the machine.** Rejected. What the mesh made would outlive
the module that made it: a container nobody manages keeps serving and stops being patched; a
unit the mesh wrote keeps running a bundle nothing updates; a name collides when the module
is assigned again. An undeclare that leaves the mesh's own work behind is an orphan factory.
**2. Keep stopping services; make "leave it running" an opt-in per resource.** Rejected. It
keeps the dangerous behaviour as the default for exactly the units that matter most — the
runtime, the ssh daemon, the network — and each new module is one forgotten field away from a
machine that goes dark when it is unassigned.
**3. The line is ownership.** Chosen.
## Decision
**Undeclaring removes what the mesh made, gives back what it changed, and leaves what was the
machine's as it is.** For a unit, that means: **the host never stops, starts, disables or
enables a unit it did not create when that unit stops being declared.** An undeclared `service`
is forgotten — reported as such — and the unit keeps whatever state it is in.
- A unit the mesh *did* create — a `process` resource's unit, which the host writes — is still
stopped and removed with it. That is the mesh's own code.
- The service's settings the mesh wrote are given back by their own resources (a kept original
restored, a region or keys removed). A running service keeps running on what it read until it
next reads its configuration; the mesh does not restart it to make it notice.
- A service declared with no `state` (ADR 0117) remains the way to say the mesh must not
**start** a unit either; this record is about what happens when a declaration goes away, and
covers every service.
- An operator who wants a unit stopped when its module goes says so first: declare it
`stopped`, push, then unassign. Stopping a machine's unit is a decision, made visibly — never
a side effect of removing a module.
## Consequences
- Unassigning the private network no longer stops the container runtime; unassigning sshd no
longer stops ssh; no uplink module can take a machine's network down on its way out.
- The host's removal report says "forgotten; the unit is the machine's" where it used to say
"stopped". A module that relied on its daemon stopping when unassigned — none in the catalogue
today does on purpose — needs the explicit step above.
- A daemon can keep running after its module is gone, on configuration that was taken back from
under it. That is a visible, running process the operator can see and stop; the alternative
was an invisible outage.
- **Not decided here:** an unassign preview that lists what an undeclare will remove and what it
will leave running. Issue 130 asks for it; it is the controller's to build.
## References
- [issue 130](../04-ISSUES/130-undeclaring-a-service-stops-it/00-report.md): the finding
- ADR 0117 (in review): the uplink modules, and a service with no state
- [ADR 0102](0102-the-mesh-writes-into-a-shared-file-never-over-it.md), [ADR 0100](0100-a-node-in-use-is-adopted-before-it-is-converged.md):
what is given back, and how
- mesh-host `internal/apply/apply.go` (`remove`, the service case)
+1
View File
@@ -200,6 +200,7 @@ python3 00-META/checks/index.py fail if stale
- **0114** — [A credential two parties hold rotates over two credentials; one a single party holds rotates in place, staged; and retiring a credential never removes what it reached](0114-a-shared-credential-rotates-over-two-credentials.md) *(proposed)*
- **0115** — [One assignment of a module per node: the module's name is the assignment's identity](0115-one-assignment-of-a-module-per-node.md) *(proposed)*
- **0117** — [A machine's uplink is a seat: the mesh configures the manager, never the link](0117-a-machines-uplink-is-a-seat.md)
- **0118** — [Undeclaring removes what the mesh made, gives back what it changed, and leaves the machine's units as they are](0118-undeclaring-leaves-the-machines-units-running.md)
### How it is built
@@ -1,7 +1,8 @@
---
status: located
opened: 2026-09-27
located-in: [mesh-host internal/apply/apply.go (remove), mesh-controller internal/overlay, mesh-catalog modules/sshd]
located-in: [mesh-host internal/apply/apply.go (remove)]
amended-design: 02-DECISIONS/0118-undeclaring-leaves-the-machines-units-running.md
---
# 130 — undeclaring a service stops it, even one the mesh only reloads or only keeps running
@@ -41,3 +42,13 @@ reaches it by.
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 0118](../../02-DECISIONS/0118-undeclaring-leaves-the-machines-units-running.md): undeclaring
removes what the mesh made, gives back what it changed, and leaves the machine's units as they
are. An undeclared `service` is forgotten, never stopped — the host did not create the unit.
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 `process`'s unit, which the host does
write, is still stopped and removed. The unassign preview asked for above is left open for the
controller.