Merge pull request 'ADR 0187: a dead tracker is not the machine's failure' (#301) from fix/a-dead-tracker-is-not-the-meshs-failure into main
This commit was merged in pull request #301.
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
---
|
||||
topic: the mesh
|
||||
status: accepted
|
||||
date: 2026-10-02
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
extends: 02-DECISIONS/0136-a-step-gates-its-module-not-the-machine.md
|
||||
---
|
||||
|
||||
# 187. A dead tracker is not the machine's failure
|
||||
|
||||
## Context
|
||||
|
||||
The home server had not applied a declaration cleanly since midday. One run-once step — the one
|
||||
that writes a media app's download clients and indexers through the app's own API — exited
|
||||
non-zero, forty-nine times over six hours, for one public tracker that had stopped answering. The
|
||||
step's own words: the entry was *written*, and the app's test of it then failed with a 400 from the
|
||||
indexer proxy. The machine reported *not doing what it was told* for the rest of the day.
|
||||
|
||||
What that gated matters more than the step. A converged machine retires the firewall it was found
|
||||
with only after a clean apply ([ADR 0168](0168-a-converged-machine-is-filtered-by-the-mesh-alone.md)),
|
||||
so that machine went on recording its found front end as merely *retired* long after the package
|
||||
had been uninstalled ([ADR 0180](0180-the-found-front-end-is-uninstalled-once-a-machine-is-converged.md)).
|
||||
A dead public tracker was holding a firewall record hostage, which is not a connection anybody
|
||||
would design.
|
||||
|
||||
The step already knew this was not its business. It had a rule for exactly this: an entry the mesh
|
||||
only *found and re-pointed*, rather than one it was told to make, whose feed is gone, is said and
|
||||
left as found — *failing the node's apply on every heartbeat for it reports the mesh as wrong about
|
||||
a tracker*. The rule was there and matched one shape of the fault. An app can refuse to save such an
|
||||
entry, and it can save it and then fail its own test; saving validates settings, and the test runs a
|
||||
live search. The rule caught the first and let the second through.
|
||||
|
||||
## Decision
|
||||
|
||||
**1. An entry the mesh only found is never the machine's failure.** Whatever shape the app's
|
||||
refusal takes — it would not save it, or it saved it and its own test fails — an indexer the mesh
|
||||
found and re-pointed is reported as a notice and left as found. What decides is whose entry it is,
|
||||
not which sentence the app returned.
|
||||
|
||||
**2. What the mesh is answerable for is the plumbing.** That the entry exists, points at this
|
||||
mesh's indexer proxy, and carries the credential the mesh delivered — which was checked against the
|
||||
proxy before anything was written. Whether a public tracker answers today is not the mesh's to
|
||||
promise, and a machine that reports itself broken because one did is lying about itself.
|
||||
|
||||
**3. An entry the operator listed is theirs to insist on.** An indexer named in the step's settings
|
||||
is one the mesh was told to make, and it still fails the step when it cannot be made to work. The
|
||||
notice says so, and says that listing the indexer is how to turn it back into a failure.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The home server applies cleanly again, and everything a clean apply gates — its found firewall's
|
||||
record among it — follows.
|
||||
- A tracker that dies is a line in a report rather than a machine that reads as broken. An operator
|
||||
who wants it gone removes the entry or repairs the feed; the mesh says which, every time it runs.
|
||||
- The four Servarr modules carry one byte-identical copy of this step each
|
||||
([ADR 0069](0069-a-module-is-a-repository-and-a-path.md)), so the change lands in four places and
|
||||
a test refuses any drift between them.
|
||||
- It does not widen to a download client: one the mesh was told to write and cannot is still a
|
||||
failure, because the mesh chose it and nothing else will fix it.
|
||||
|
||||
## How this is checked
|
||||
|
||||
| Rule | Checked by |
|
||||
|---|---|
|
||||
| A found feed whose tracker answers an error after the entry was written is a notice | the step's tests, with the home server's own message and the app's two validations modelled apart |
|
||||
| An indexer the settings list is still a failure | the same test |
|
||||
| The four copies of the step do not drift | the step's own sameness test |
|
||||
| Live | the home server applies cleanly, and its found firewall reads *removed* |
|
||||
|
||||
## References
|
||||
|
||||
- [ADR 0136](0136-a-step-gates-its-module-not-the-machine.md), [ADR 0168](0168-a-converged-machine-is-filtered-by-the-mesh-alone.md), [ADR 0180](0180-the-found-front-end-is-uninstalled-once-a-machine-is-converged.md), [ADR 0069](0069-a-module-is-a-repository-and-a-path.md)
|
||||
@@ -187,6 +187,7 @@ python3 00-META/checks/index.py fail if stale
|
||||
- **0184** — [A service the mesh asked to run is still running a moment later](0184-a-service-the-mesh-asked-to-run-is-still-running-a-moment-later.md)
|
||||
- **0185** — [A control plane behind its seat's row serves what it can](0185-a-control-plane-behind-its-seats-row-serves-what-it-can.md)
|
||||
- **0186** — [A ban list never holds a neighbour, and the mesh's own bans are its own wherever they hang](0186-a-ban-list-never-holds-a-neighbour.md)
|
||||
- **0187** — [A dead tracker is not the machine's failure](0187-a-dead-tracker-is-not-the-machines-failure.md)
|
||||
|
||||
### Its tiers, from the bottom up
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ code:
|
||||
- mesh-host internal/apply/apply.go
|
||||
- mesh-tools src/main.ts
|
||||
- mesh-catalog modules/mesh-catalog
|
||||
updated: 2026-09-28
|
||||
updated: 2026-10-02
|
||||
decisions:
|
||||
- 02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md
|
||||
- 02-DECISIONS/0126-a-module-declares-its-own-seats.md
|
||||
@@ -24,6 +24,7 @@ decisions:
|
||||
- 02-DECISIONS/0129-a-seat-carries-the-protocol-of-its-role.md
|
||||
- 02-DECISIONS/0135-a-module-version-prepares-its-state-before-it-runs.md
|
||||
- 02-DECISIONS/0136-a-step-gates-its-module-not-the-machine.md
|
||||
- 02-DECISIONS/0187-a-dead-tracker-is-not-the-machines-failure.md
|
||||
- 02-DECISIONS/0134-the-mesh-says-what-it-applied.md
|
||||
---
|
||||
|
||||
@@ -470,6 +471,21 @@ moment the mesh can mint for itself: **the bus's own accounts** (§the bootstrap
|
||||
needs an account before it can run) and **the vault's own credential**. Any third exception is a
|
||||
design failure, and naming these two is what makes a third one visible.
|
||||
|
||||
## What a step is answerable for, 2026-10-02
|
||||
|
||||
[ADR 0187](../../02-DECISIONS/0187-a-dead-tracker-is-not-the-machines-failure.md). A step gates its
|
||||
module and not the machine ([ADR 0136](../../02-DECISIONS/0136-a-step-gates-its-module-not-the-machine.md)),
|
||||
but a step that exits non-zero still leaves the machine reporting that it is not doing what it was
|
||||
told — and a clean apply gates other things entirely, the found firewall's retirement among them. So
|
||||
what a step calls a failure matters beyond the step.
|
||||
|
||||
The rule the media step now follows, and the one to copy: a step fails for what the mesh chose and
|
||||
can fix, and reports what it merely found and cannot. An indexer entry the mesh re-pointed at this
|
||||
mesh's proxy is plumbing the mesh is answerable for; whether the public tracker behind it answers
|
||||
today is not. An entry the operator listed is the operator's to insist on, and still fails. Six
|
||||
hours of a machine reading as broken, for one tracker that had died, is what the distinction costs
|
||||
when it is missing.
|
||||
|
||||
## 11. Open
|
||||
|
||||
**Semantic change has no mechanical defence** (§8). Recorded as open rather than solved, because
|
||||
|
||||
Reference in New Issue
Block a user