diff --git a/02-DECISIONS/0187-a-dead-tracker-is-not-the-machines-failure.md b/02-DECISIONS/0187-a-dead-tracker-is-not-the-machines-failure.md new file mode 100644 index 0000000..ebf867e --- /dev/null +++ b/02-DECISIONS/0187-a-dead-tracker-is-not-the-machines-failure.md @@ -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) diff --git a/02-DECISIONS/README.md b/02-DECISIONS/README.md index 5a444bd..bbff2dc 100644 --- a/02-DECISIONS/README.md +++ b/02-DECISIONS/README.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 diff --git a/03-DESIGN/01-to-be/32-what-a-module-declares.md b/03-DESIGN/01-to-be/32-what-a-module-declares.md index a67f86b..1cf0358 100644 --- a/03-DESIGN/01-to-be/32-what-a-module-declares.md +++ b/03-DESIGN/01-to-be/32-what-a-module-declares.md @@ -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