Files
hq/02-DECISIONS/0119-a-taken-tunnels-predecessor-is-retired.md
T
jschoubben ce6ae943b7 Merge main: renumber this branch's records around the trunk's
Both lines of work numbered from the same point, so four decision records and one design
document existed twice with different content. The trunk keeps its numbers and this branch
yields — the only rule that scales, because the trunk's are already cited by what merged
before them.

  0117 the bus is the only broker        -> 0125
  0118 a module declares its own seats   -> 0126
  0119 amqp is a provision, not the bus  -> 0127
  0120 the mesh bus is required          -> 0128
  0123 a seat carries its role's protocol -> 0129
  0124 the predecessor is ending          -> 0130
  design 29, what a module declares       -> design 32

Applied to the code repositories too, because a stale reference is worse when numbers
collide than when they dangle: the reader lands on a real record that decided something
else.

Two reconciliations the merge forced, both real:

**0110 was marked wholly superseded and was not.** Its successor says in as many words that
everything 0110 decided about what a seat *is* stands untouched — and two records that
landed on the trunk rest on exactly that part. So it is accepted again, extended rather than
replaced, with a note saying which of its claims moved and where.

**A seat's protocol becomes columns, not fields.** The trunk moved the seat set out of
compiled code into a table the controller owns. This branch had added what a role accepts,
emits and serves to the Go slice. The decision is unaffected and the mechanism is better for
it: giving a role a protocol is now a write rather than a rebuild, which is the trunk's own
argument applied to what this branch added.

One check still fails and it fails on main too: a record resting on ADR 0112 while that is
still 'proposed'. Left alone — it is not this merge's to answer.
2026-09-27 18:23:41 +02:00

88 lines
5.1 KiB
Markdown

---
topic: the mesh
status: accepted
date: 2026-09-27
deciders: jochen
reconstructed: false
extends: 0105-the-mesh-adopts-the-predecessors-tunnel-in-place.md
---
# 119. A taken tunnel's predecessor is retired once the take is proven
## Context
[ADR 0105](0105-the-mesh-adopts-the-predecessors-tunnel-in-place.md) has the private network take
over the tunnel it finds: the found unit stopped and disabled, never flushed, and **its
configuration left on disk, kept like any held file.** That was the right caution for the take
itself — if the mesh's interface failed to come up, the host starts the found unit again and the
peers never notice — and every apply since stops the found unit again should anyone start it.
What it leaves is a predecessor that never finishes leaving. On every machine that has enrolled,
the tunnel is the mesh's and has been proven so — its interface up with the found key, the peers
handshaking, the machines resolving and reaching each other over it — and still the predecessor's
configuration sits where its unit reads it, held for a module that has long since replaced it.
The predecessor itself is being deprecated. A tunnel that can be started again by one command, with
a configuration nothing maintains any more, is not a rollback path; it is a second way onto the
network that nobody is watching. And the hold never ends, so every node report keeps listing it.
## Considered Options
**1. Keep it, as 0105 says.** Rejected: the caution it bought is spent once the take is proven, and
what remains is a live, unmaintained way back onto the network.
**2. Delete it at the take.** Rejected: the take is exactly the moment the fallback is needed. If
the mesh's interface does not come up, the host must still be able to raise the found one.
**3. Retire it once the take is proven.** Chosen.
## Decision
**Once the mesh's interface has proven it carries the tunnel, the found interface's configuration
is removed from where its unit reads it.**
- **Proven means:** the tunnel's state is *taken* — the found unit down and disabled, the mesh's
interface up with the found key — and the mesh's interface has completed a handshake with at
least one peer. Not before: until then, a failed take still falls back to the found unit.
- **Retired means:** the configuration file the found unit reads is removed. Its original was
already kept, before anything happened to it
([ADR 0100](0100-a-node-in-use-is-adopted-before-it-is-converged.md)), and stays kept; that copy
is the record of what the predecessor was, and a person's way back if one is ever wanted.
- The found unit stays disabled. Without its configuration it cannot raise the interface, so the
every-apply stop that guarded against it becomes a check that finds nothing to do.
- **The hold ends.** What was held for the private network has been replaced; the node stops
reporting it.
- **The mesh never brings it back.** Undeclaring the private network does not restore the found
tunnel: the mesh stopped it, and nothing is started on the way out
([ADR 0126](0118-undeclaring-gives-a-unit-back-the-state-it-was-found-in.md)). A machine whose
private network is unassigned has no tunnel until it is assigned again — which is what
unassigning it means.
## Consequences
- On every machine that took a tunnel, the predecessor's tunnel configuration disappears at the
first apply after the take is proven. Nothing a peer sees changes; the mesh's interface already
carries the same key, port, address and peers.
- A take that is never proven — no peer ever handshakes — keeps the found configuration, and the
node says so, so a broken take is visible rather than silently retired.
- Rolling back to the predecessor's tunnel becomes a deliberate act, in this order: **unassign the
private network first**, then copy the kept original back and start its unit. The mesh does
neither. While the private network is still assigned, the tunnel is the mesh's: a restored
configuration is held and retired again at the next proven apply, and the found unit cannot
bind the port the mesh's interface holds. The node says so when it happens.
- A configuration something keeps writing back — the predecessor's own tooling, say — is retired
again each time it appears, but the first original stays the one kept; a different content is
kept once beside it, and the node reports that the configuration came back.
- The host retires only the found interface's own configuration file (`/etc/wireguard/<iface>.conf`),
never a path the mesh writes, and never a link: a configuration that is a link to somewhere else
is left, with its target, for a person to retire.
- 0105's "its configuration stays on disk, kept like any held file" holds until the take is proven,
and not after.
## References
- [ADR 0105](0105-the-mesh-adopts-the-predecessors-tunnel-in-place.md): the take, and why it keeps
the found configuration during it
- [ADR 0100](0100-a-node-in-use-is-adopted-before-it-is-converged.md): kept originals
- [ADR 0126](0118-undeclaring-gives-a-unit-back-the-state-it-was-found-in.md): nothing is started on the way out
- mesh-host `internal/apply/takeover.go`