Designs 08 and 05 revised; 141 resolved by ADR 0140 and 084 by ADR 0102 and issue 128, both by reading; 143 and 144 decided, built on the matching branches in mesh-host and mesh-controller, resolved when the home server's record names the predecessor's chain.
111 lines
6.2 KiB
Markdown
111 lines
6.2 KiB
Markdown
---
|
|
status: located
|
|
opened: 2026-09-29
|
|
located-in:
|
|
- mesh-host internal/apply/opening.go (retireFirewall)
|
|
- mesh-host internal/apply/apply.go (the condition it is called under)
|
|
fixed-by:
|
|
amended-design:
|
|
---
|
|
|
|
# 143 — Converging a machine does not retire the firewall it found, and says it does
|
|
|
|
## What was observed
|
|
|
|
The control-node was converged on 2026-09-29, the first machine with a found firewall to be flipped —
|
|
the two converged before it had none.
|
|
|
|
The preview said, and the flip repeated:
|
|
|
|
```
|
|
the found firewall (ufw) is disabled, never flushed: its configuration stays on disk
|
|
...
|
|
sent: the host loads the mesh's filter and disables the firewall it found
|
|
```
|
|
|
|
The mesh then reported the node `converged`, 372 resources applied, nothing failed. Afterwards, on the
|
|
machine:
|
|
|
|
```
|
|
systemctl is-enabled ufw -> enabled
|
|
systemctl is-active ufw -> active
|
|
```
|
|
|
|
*Corrected 2026-09-29, an hour later, from reading the host rather than the declaration.* **The first
|
|
account of this was wrong.** It said the declaration carries no resource that would disable the found
|
|
firewall, and that the sentence was printed by the command with nothing implementing it. The
|
|
declaration indeed carries no such resource — but the mechanism was never meant to be one. It is a
|
|
step in the host's own apply, `retireFirewall`, and it exists, is careful, and is strict: it refuses to
|
|
retire anything until it has read back from the machine that the mesh's own table is loaded, it records
|
|
the forward policies first so a half-done retirement can be retried, and it verifies ufw reports
|
|
inactive afterwards.
|
|
|
|
What is established is narrower and stranger than "nothing implements it":
|
|
|
|
- ufw was **active and enabled two minutes after the flip**, and the flip had reported the node
|
|
converged with 372 resources applied and nothing failed.
|
|
- The machine's own record now reads `disabled_by_mesh: true` — but it was written by a reconcile
|
|
*after* an operator disabled ufw by hand, roughly fifty minutes later. A reconcile found ufw already
|
|
inactive, asked it to be inactive, read that back, and recorded that the mesh had done it.
|
|
- So the step did not take effect at the flip, and the machine's record now says it did.
|
|
|
|
The candidates are named rather than chosen, because the evidence does not separate them: the step is
|
|
called only when the apply had no failures, and a skipped step is silent; the mesh's table is loaded by
|
|
a service in the same apply, so whether it was loaded *at the moment the step asked* is an ordering
|
|
question; and the host's own detail lines do not reach the journal, so what it decided is not
|
|
recoverable after the fact.
|
|
|
|
## Why it matters beyond this instance
|
|
|
|
**It is a stated behaviour that does not happen, reported as success** — the fault this repository
|
|
exists to catch, and
|
|
[ADR 0100](../../02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md) states it as
|
|
part of what the flip *is*: "loads the mesh's derived filter in place of its refusal-only table, and
|
|
retires the found firewall by disabling it, never by flushing".
|
|
|
|
**It could only be found on the first machine that had one.** The two machines converged before this
|
|
had no firewall to retire, so the step had never run, and nothing reported that it had not. That is
|
|
the same shape as [issue 136](../136-a-module-may-name-a-program-the-machine-does-not-have/00-report.md):
|
|
a step that is silent when it does nothing.
|
|
|
|
**The machine is left doubly filtered, which is not what either firewall describes.** Every base chain
|
|
at a hook runs and a drop in any is final, so the machine now enforces the *intersection* of the mesh's
|
|
derived filter and a rule set left by the system being replaced. Nothing is broken by that today —
|
|
measured from outside, mail, the proxy and git-over-ssh answer and the databases and admin interfaces
|
|
are refused — but the machine's behaviour is described by neither of the two things claiming to
|
|
describe it, and the stale set includes a rule for a broker that no longer exists.
|
|
|
|
**And returning the node to adopted would be wrong in the other direction.** ADR 0100 says that
|
|
restores the found firewall by enabling it again; enabling something that was never disabled is
|
|
harmless, but the mesh's belief about which firewall is in force has been wrong in both modes.
|
|
|
|
## Open questions
|
|
|
|
- Which side owns retiring it — a resource in the declaration, so it is applied and reported like
|
|
everything else, or the flip as an act? A resource seems right: the flip is otherwise entirely
|
|
expressed as one, and an act that only the command performs cannot be re-checked on a later
|
|
reconcile.
|
|
- What should a reconcile do if the found firewall is enabled again by hand, or by a package update?
|
|
Convergence is a state, so presumably re-disable it and say so.
|
|
- Should the preview say what it *will* do rather than what it does, until a step exists that does it?
|
|
The wording was read as evidence twice in one session.
|
|
- Is there a check that a sentence the mesh prints corresponds to something that happened? This is the
|
|
second time in one session that a printed claim and the machine disagreed.
|
|
- **Why did the step not take effect?** It is called only when the apply had no failures, and being
|
|
skipped is silent. The mesh's table is loaded by a service in the same apply, so whether it was
|
|
loaded when the step asked is an ordering question — and ADR 0100 makes loading it first a
|
|
precondition rather than an expectation.
|
|
- **A step that records the mesh as having done what an operator did is worse than the omission.** The
|
|
record now says the mesh disabled ufw. Nothing distinguishes "we did this" from "we found it already
|
|
so". Should it?
|
|
- Why do the host's own detail lines not reach the journal? Everything it decided during the flip is
|
|
unrecoverable, which is why this account has candidates instead of a cause.
|
|
|
|
## Decided, 2026-10-02
|
|
|
|
[ADR 0168](../../02-DECISIONS/0168-a-converged-machine-is-filtered-by-the-mesh-alone.md), rule 1:
|
|
convergence is a state the host keeps — the found firewall active again is retired again and said, a
|
|
reconcile that finds it inactive records *found so* and never *done by the mesh*, and a step skipped
|
|
after a failed apply is said. Built in mesh-host on `feat/one-thing-filters-a-converged-machine`; the
|
|
record of both machines of this mesh is corrected by the first report under it.
|