Compare commits

...
Author SHA1 Message Date
jochen c58f3dc765 ADR 0280: the resolver restarts itself when it stops answering, by the operator's word (issue 348)
mesh/delivery ready: it delivers once merged
mesh/merge-gate out of date: judged against a trunk it does not name, the trunk is now a0e51c061b5e; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against a trunk it does not name, the trunk is now a0e51c061b5e; judged again on a recheck or when it is merged
ADR 0240 rule 6 left a restart on health to a record of its own. The resolver's
failure cuts its machine off by name, so no healer in the controller can reach
it; the module repairs itself, braked, and says it as its health. To-be 48 and
ADR 0240 point to the exception.
2026-10-10 17:47:49 +02:00
3 changed files with 121 additions and 2 deletions
@@ -181,6 +181,12 @@ gate waits rather than fails. What a consumer finds while its provider is health
reaches a person through the operator's conversation, or a healer; a healer that restarts on health is its
own decision.
> **The mechanism changed — 2026-10-10, by ADR 0280.** Rule 6 still stands for the node-engine and for
> every module but one: the dnsmasq module restarts its own resolver when the resolver stops answering,
> braked and said as its health, by the operator's word
> ([ADR 0280](0280-the-resolver-restarts-itself-when-it-stops-answering-braked-and-said-as-its-health.md)).
> Any other restart on health still needs its own record.
**7. A declaration is proved before it is trusted.** `module check` refuses a `health` field that names an
endpoint the module does not declare, an interval, timeout or count outside its bounds, or a tool the module
does not serve. A bed from mesh-lab, run by the catalogue's check on the build seat, starts every resource
@@ -0,0 +1,106 @@
---
topic: what runs on it
status: accepted
date: 2026-10-10
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0240-a-module-says-how-it-is-healthy-and-the-node-engine-judges-it.md
---
# 280. The resolver restarts itself when it stops answering, braked, and says it as its health
## Context
[ADR 0240](0240-a-module-says-how-it-is-healthy-and-the-node-engine-judges-it.md) rule 6 says nothing is
restarted for being unhealthy, and that "a healer that restarts on health is its own decision"; its
options left that to "a record of its own under ADR 0231". This is that record, for one module.
[Issue 348](../04-ISSUES/348-a-resolver-that-stopped-answering-ran-on-unnoticed-and-builds-sent-after-it-were-blamed/00-report.md):
on 2026-10-09 the mesh's resolver on the anchor refused every question on its private address for
twenty minutes (10:57:57 to 11:18:12 UTC) while its unit stayed active and its journal said nothing.
Loopback still answered. The certificate authority failed its health check from 10:59:37 UTC, and builds
sent in that window were blamed for a failure that was the resolver's. Two facts shape the answer:
- **The cause is a known failure of the resolver itself.** dnsmasq 2.93 closes every listener when an
address re-read fails while a bridge is torn down; reproduced in a network namespace, 53 closings in
120 teardowns. The catalogue's fix binds each address once at start (`bind-interfaces`), which removes
the known cause, but a resolver that stops answering for another reason looks the same.
- **A resolver that stops answering can cut its machine off from the rest of the mesh by name.** Then
nothing outside the machine reaches it to repair it: not the gate's rollback
([ADR 0236](0236-a-build-is-judged-on-its-first-machine-and-put-back-by-something-other-than-itself-and-so-it-rolls-out-unattended.md)),
and not a healer in the controller
([ADR 0231](0231-a-healer-acts-on-what-observation-raised-and-only-observation-says-it-worked.md)),
which acts by sending to the machine over the bus.
## Considered Options
1. *Keep rule 6 and let the condition reach the operator.* Rejected: the operator is often asleep or away,
and twenty minutes of a dead resolver on the anchor stalls every machine that resolves through it. The
restart is what the operator did by hand on 2026-10-09.
2. *A healer row in the controller's registry (ADR 0231) that restarts the unit.* Rejected for this case:
the repair must reach a machine whose name lookups are failing, and the controller reaches a machine
only through the bus. It remains the right shape for a repair that does not cut its own path.
3. **The module repairs its own resolver, as ADR 0224 §5 lets a provider repair a known failure:
braked, observed by its next look, and said as its health.** Chosen.
## Decision
**The dnsmasq module restarts its own resolver when the resolver stops answering, and only then.** Its
long-running watch asks the machine's own name on every listen address, over UDP and TCP, every 15
seconds. After two failing looks in a row it restarts the resolver's unit. It restarts only a unit that is
running; a stopped or failed unit is the service manager's and the operator's.
**The repair is braked.** At most one restart every two minutes and three within an hour. The budget is
kept in the watch's state on the machine, so a restart of the watch itself does not reset it. When the
budget is spent the watch restarts nothing more until the oldest restart is an hour old, and says why.
**Only the next look says whether it worked** (ADR 0231's rule, applied in the module). A restart helped
only when a look within a minute after it answered. The module's declared health check (`dnsmasq_health`,
ADR 0240 rule 2) reads the watch: a restart that helped leaves the module healthy; one that did not, or a
spent budget, is unhealthy, which the node-engine states and the controller raises as
`module.dnsmasq.<machine>.unhealthy`. The operator hears of it then.
**Every restart is said**, as the module's `restarted` event and in the watch's state, which
`dnsmasq_health` shows.
**The operator's word.** Rule 6 and the catalogue change (mesh-catalog pull request 161) both said this
departure needed the operator's word or a healer record. The operator gave it on 2026-10-10 by approving
the merge, knowing that it makes the resolver restart itself.
**This is one module's exception, not a change to rule 6.** Any other module that wants to restart on
health needs its own record, or a healer row under ADR 0231.
## Consequences
- A resolver that stops answering is back within about 30 seconds plus its start time, instead of after a
person notices it.
- **A module's own repair is not a healer.** It is not counted by the controller's mesh-wide brake, and
it is not listed by the controller's `healers` verb. Its own brake (three an hour) is what bounds it,
and its `restarted` event and health state are where it is seen.
- A restart hides a failure for as long as it helps. The evidence of each one is kept in the event and
the watch's state, so a resolver that is restarted every hour is still visible there. Nothing raises a
condition for repeated restarts that each helped; that is not built, and the operator hears only of a
restart that did not help.
- The rule 6 test in the node-engine still holds: the node-engine restarts nothing. The restart is the
module's, made through the service manager on its own machine.
## References
- [ADR 0240](0240-a-module-says-how-it-is-healthy-and-the-node-engine-judges-it.md) rule 6, which this
record makes one exception to.
- [ADR 0231](0231-a-healer-acts-on-what-observation-raised-and-only-observation-says-it-worked.md) — the
rules a repair keeps: braked, and only observation says it worked.
- [ADR 0224](0224-a-provider-that-keeps-failing-a-consumer-is-a-problem-the-controller-reports.md) §5 —
a module repairing a known failure of its own.
- [Issue 348](../04-ISSUES/348-a-resolver-that-stopped-answering-ran-on-unnoticed-and-builds-sent-after-it-were-blamed/00-report.md).
- mesh-catalog pull request 161, branch `fix/the-resolver-answers-or-is-restarted`, `modules/dnsmasq`.
## How it is checked
| What | Checked by |
|---|---|
| two failing looks restart the unit, within the gap and the hourly budget | `TestTheWatchRestartsOnASecondFailingLookWithinItsBudget` (mesh-catalog `modules/dnsmasq`) |
| a restart helped only when a look soon after it answered; health says whether it did | `TestTheWatchRestartsAResolverThatStoppedAnsweringAndHealthSaysWhetherItHelped`, `TestARestartHelpedOnlyWhenTheResolverAnsweredSoonAfterIt` |
| only a running unit is restarted | `TestTheWatchRestartsOnlyAUnitThatRuns` |
| the budget survives the watch's own restart | `TestTheWatchLoopKeepsItsBudgetAcrossItsOwnRestart` |
| live | `dnsmasq_health` on each machine that runs the resolver: its restarts within the hour and why |
@@ -2,7 +2,7 @@
layer: to-be
status: in-progress
code: [mesh-host, mesh-controller, mesh-lab, mesh-catalog]
updated: 2026-10-08
updated: 2026-10-10
decisions:
- 02-DECISIONS/0254-a-wait-for-a-persons-new-login-is-not-a-gates-failure-and-each-module-of-a-send-keeps-its-own-pass.md
- 02-DECISIONS/0240-a-module-says-how-it-is-healthy-and-the-node-engine-judges-it.md
@@ -10,6 +10,7 @@ decisions:
- 02-DECISIONS/0247-a-machine-with-a-vpn-client-routes-names-by-domain-through-a-resolver-of-its-own.md
- 02-DECISIONS/0236-a-build-is-judged-on-its-first-machine-and-put-back-by-something-other-than-itself-and-so-it-rolls-out-unattended.md
- 02-DECISIONS/0227-the-core-holds-nine-rules-each-checked-and-is-built-to-them-in-six-phases.md
- 02-DECISIONS/0280-the-resolver-restarts-itself-when-it-stops-answering-braked-and-said-as-its-health.md
- 02-DECISIONS/0256-a-machines-hardware-condition-is-read-by-one-module-on-every-machine-and-a-fault-is-said-through-its-health.md
---
@@ -209,6 +210,11 @@ The runtime restarts what exits, as now. The node-engine never restarts, recreat
being unhealthy. The condition reaches the operator through the conversation (to-be 46), or a healer once a
record gives one that act.
One module repairs itself, by its own record: the dnsmasq module's watch restarts the resolver's unit when
it stops answering, at most once every two minutes and three times an hour, and says through its declared
health whether the restart helped ([ADR 0280](../../02-DECISIONS/0280-the-resolver-restarts-itself-when-it-stops-answering-braked-and-said-as-its-health.md)). The node-engine still restarts
nothing; any other restart on health needs a record of its own.
## 8. A declaration is proved before it is trusted
- **`module check`** refuses a `health` field naming an endpoint the module does not declare under
@@ -483,7 +489,8 @@ controller.
## What is not decided here
- Whether a healer restarts what stays unhealthy (its own record, under ADR 0231).
- Whether a healer restarts what stays unhealthy (its own record, under ADR 0231). The resolver's own
restart is decided, for that module alone, by ADR 0280.
- Health of scheduled work, and of what a module's events do (issue 276).
- A slow starter that needs more than five minutes to be ready — a change to the bound, recorded, when one
exists.