ADR 0187: a dead tracker is not the machine's failure; design 32
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)
|
- **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)
|
- **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)
|
- **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
|
### Its tiers, from the bottom up
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ code:
|
|||||||
- mesh-host internal/apply/apply.go
|
- mesh-host internal/apply/apply.go
|
||||||
- mesh-tools src/main.ts
|
- mesh-tools src/main.ts
|
||||||
- mesh-catalog modules/mesh-catalog
|
- mesh-catalog modules/mesh-catalog
|
||||||
updated: 2026-09-28
|
updated: 2026-10-02
|
||||||
decisions:
|
decisions:
|
||||||
- 02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md
|
- 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
|
- 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/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/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/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
|
- 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
|
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.
|
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
|
## 11. Open
|
||||||
|
|
||||||
**Semantic change has no mechanical defence** (§8). Recorded as open rather than solved, because
|
**Semantic change has no mechanical defence** (§8). Recorded as open rather than solved, because
|
||||||
|
|||||||
Reference in New Issue
Block a user