ADR 0189: the store keeps what the records name, and a maintenance step holds its writers still

Issue 108: the artifact store has never collected anything. Fifty-three
repositories on the machine that serves everything else, and the only outcome
of leaving it is a full disk reported as somebody else's failure.

The mesh decides what may go — from its own build records, so it never names
a digest it did not put there — and the store reclaims the bytes in a nightly
window with its server held still. Deletion on the one door takes nothing a
push did not already have.

Designs 18 and 20 amended; issue 108 resolved.

Also issue 202, found running the controller's suite: a module whose required
setting nobody set is left out of the machine in silence, and dnsmasq became
that module this morning.
This commit is contained in:
2026-10-02 21:48:48 +02:00
parent 0b9fc90885
commit b69ae663bc
6 changed files with 298 additions and 6 deletions
@@ -1,9 +1,9 @@
---
status: open
status: resolved
opened: 2026-09-23
located-in: []
fixed-by:
amended-design:
located-in: [mesh-controller internal/inventory, mesh-controller internal/artifacts, mesh-host internal/apply, mesh-catalog modules/distribution]
fixed-by: 02-DECISIONS/0189-the-store-keeps-what-the-records-name.md
amended-design: 03-DESIGN/01-to-be/18-building-a-module.md
---
# 108 — The registry has no garbage collection, and two doors make it harder to add
@@ -75,3 +75,32 @@ real thing services need, and the mesh cannot express one.
images by digest and moves by version — is retention "the digests no recorded build names"?
- Who owns the routine when the store and its public door are two modules — the store, since the
volume is its?
## Answered, 2026-10-02 — [ADR 0189](../../02-DECISIONS/0189-the-store-keeps-what-the-records-name.md)
The three open questions, answered:
- **A maintenance step, or a backend that does not need its writers stopped?** The step. A
scheduled container may name `while-stopped` — resource ids of **its own module's** containers,
which the host stops before the run and starts again after it whatever the step did. A storage
backend the mesh does not run would be a bigger thing to own than the mechanism it avoids, and
the mechanism is wanted anyway: a service that cannot have work done underneath it is a real
shape and the mesh could not express it at all.
- **Is retention "the digests no recorded build names"?** Nearly. An artifact stays because a
definition the mesh holds names it (no age limit), or because it belongs to one of the five most
recent successful builds of its module. Last-N-tags was the predecessor's rule for a registry
that knew nothing else; this mesh knows what each digest is for.
- **Who owns the routine now the second door is gone?** Both halves, each where it can be. The
**mesh** decides what may go — only it holds the records — and asks the store to drop it. The
**store** reclaims the bytes, because only it can stop its own server. Neither half can be done
by the other.
And the sharpened point — enabling deletion on a door with no accounts — dissolved on inspection:
**that door already accepts a push**, so a writer who can reach it can already replace any tag.
Delete takes nothing a push did not have. What it does not do is undo ADR 0082's bargain, which
putting an authenticated door in front of deletion would have.
The second registry process is not built, as the 2026-09-26 note says, so the shared blob cache
and the deletion-cached-by-the-other-door problem never arise. Plain `garbage-collect` is enough:
what the mesh keeps is still a manifest in the store, so `--delete-untagged` — the flag that would
delete images machines are running — is not needed at all.
@@ -0,0 +1,67 @@
---
status: open
opened: 2026-10-02
located-in: [mesh-catalog modules/dnsmasq, mesh-controller cmd/mesh-controller]
fixed-by:
amended-design:
---
# 202 — A module whose required setting nobody set is left out of the machine, and the resolver is the module it happened to
## What was observed
Running the controller's own test suite against the catalogue beside it, 2026-10-02.
`TestTheResolverIsToldEveryMachineOnTheNetworkAndToldAgainWhenOneLeaves` fails with *"the resolver
was not handed the machines"*. Composing the same machine by hand and listing what it receives
shows why: **dnsmasq contributes nothing at all.** Four resources are composed for that node, all
of them the overlay's. The resolver's package, its configuration, its service and the fact that
carries every machine's name are simply not there.
The cause is one line added to `dnsmasq`'s configuration earlier the same day: the addresses it
listens on beside the machine's own became an operator setting,
`listen-address=${setting:listen-addresses}`, with no default. A `${setting:…}` nothing sets is
refused, a module that cannot be composed is **left out** rather than failing the whole machine
([ADR 0163](../../02-DECISIONS/0163-taking-a-module-over-is-a-comparison.md)), and so a node
assigned the resolver is handed a declaration with no resolver in it.
The failing test is the symptom that surfaced it. The test is not what is wrong.
**Proven rather than inferred.** Composing the same machine a second time with
`listen-addresses` set to `127.0.0.1` and nothing else changed, every one of dnsmasq's eight
resources appears — `needs-broker`, `mesh-state`, `package`, `config`, `runtime-dns`, `runtime`,
`service` and `fact-node-zones`. The only difference between a machine with a resolver and a
machine without one is whether somebody set a value that did not exist yesterday.
## Why it matters beyond this instance
**Leaving a module out is right, and being quiet about it is not.** The rule exists so one
module's broken setting cannot stop a machine converging — a good rule. But the outcome here is a
machine that applies cleanly, reports current, and is missing its DNS resolver. Every name on that
machine then resolves through whatever was there before, or not at all, and nothing in the mesh
says the resolver was dropped. That is the shape
[issue 152](../152-a-nodes-plan-failure-silently-drops-its-routed-names/00-report.md) records for
routed names, here for a whole module.
**And a setting with no default is a definition that cannot be assigned.** Every other
`${setting:…}` in the catalogue names something that is genuinely particular to one installation —
a public domain, an issuer. "Which addresses besides my own do I answer on" has an obvious correct
default for every machine that is not a LAN gateway: none beside loopback. A definition that
refuses to compose until somebody sets a value most machines do not need is a definition that
breaks the next node to be assigned it, and genesis with it.
## What this does not claim
Whether the live machines are affected was not checked — those four have had the setting set, or
their resolvers would already be gone. The claim is about a machine assigned the resolver *from
now on*, and about the silence.
## Open questions
- Should the declaration say which modules it left out, where a person or the console can see it?
`left_out` already travels to the host ([ADR 0163](../../02-DECISIONS/0163-taking-a-module-over-is-a-comparison.md));
what is missing is anything that reads it back and says so.
- Should a `${setting:…}` be allowed a default in the definition — making "unset" mean "the
default" rather than "refuse" — or is a setting with a default no longer the operator's value?
- Is leaving a module out ever right for a module a node is **assigned**, as opposed to one it
merely pulls in? An assignment is somebody saying *this machine runs this*; silently not running
it is the one answer nobody asked for.