From 8a78ff4efef3448a1f2e27b81f6151baff8c9cd3 Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 27 Sep 2026 00:40:31 +0200 Subject: [PATCH 1/4] ADR 0119: a taken tunnel's predecessor is retired once the take is proven --- ...-a-taken-tunnels-predecessor-is-retired.md | 78 +++++++++++++++++++ 02-DECISIONS/README.md | 1 + 2 files changed, 79 insertions(+) create mode 100644 02-DECISIONS/0119-a-taken-tunnels-predecessor-is-retired.md diff --git a/02-DECISIONS/0119-a-taken-tunnels-predecessor-is-retired.md b/02-DECISIONS/0119-a-taken-tunnels-predecessor-is-retired.md new file mode 100644 index 0000000..89db5b1 --- /dev/null +++ b/02-DECISIONS/0119-a-taken-tunnels-predecessor-is-retired.md @@ -0,0 +1,78 @@ +--- +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 0118, in review). 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: copy the kept original back + and start its unit. The mesh does neither. +- 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 0118 (in review): nothing is started on the way out +- mesh-host `internal/apply/takeover.go` diff --git a/02-DECISIONS/README.md b/02-DECISIONS/README.md index 350db19..aae6bfc 100644 --- a/02-DECISIONS/README.md +++ b/02-DECISIONS/README.md @@ -133,6 +133,7 @@ python3 00-META/checks/index.py fail if stale - **0105** — [The mesh adopts the predecessor's tunnel in place](0105-the-mesh-adopts-the-predecessors-tunnel-in-place.md) - **0106** — [The bus is NATS](0106-the-bus-is-nats.md) - **0116** — [The bus is built in five steps, and the protocol moves with it](0116-the-bus-is-built-in-five-steps.md) +- **0119** — [A taken tunnel's predecessor is retired once the take is proven](0119-a-taken-tunnels-predecessor-is-retired.md) ### Its tiers, from the bottom up -- 2.54.0 From 2f195d501e0a9d498818d738d46ce9ef9e5419ac Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 27 Sep 2026 00:40:57 +0200 Subject: [PATCH 2/4] to-be 08: the found tunnel's configuration is retired once the take is proven (ADR 0119) --- 03-DESIGN/01-to-be/08-connectivity.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/03-DESIGN/01-to-be/08-connectivity.md b/03-DESIGN/01-to-be/08-connectivity.md index ae7801f..2337889 100644 --- a/03-DESIGN/01-to-be/08-connectivity.md +++ b/03-DESIGN/01-to-be/08-connectivity.md @@ -7,11 +7,12 @@ code: - mesh-controller internal/identity/authority.go - mesh-host internal/identity/serving.go - mesh-host internal/apply (the service that reflects a rule set) -updated: 2026-09-25 +updated: 2026-09-27 decisions: - 02-DECISIONS/0104-a-provision-may-be-answered-by-an-adapter-to-the-predecessor.md - 02-DECISIONS/0106-the-bus-is-nats.md - 02-DECISIONS/0105-the-mesh-adopts-the-predecessors-tunnel-in-place.md + - 02-DECISIONS/0119-a-taken-tunnels-predecessor-is-retired.md - 02-DECISIONS/0108-a-route-carries-the-policy-applied-to-a-request.md - 02-DECISIONS/0103-what-an-adopted-node-holds-and-what-its-guard-refuses.md - 02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md @@ -768,6 +769,12 @@ where a found tunnel is left running beside the mesh's; where it is adopted ther The guard admits the mesh's ports from that one interface, and the predecessor's peers arrive on it. +*2026-09-27, [ADR 0119](../../02-DECISIONS/0119-a-taken-tunnels-predecessor-is-retired.md).* The +found configuration is kept only until the take is proven — the found unit down, the mesh's +interface up and handshaking with a peer. Then it is removed from where the found unit reads it +(its original stays kept), the hold ends, and the predecessor's tunnel cannot be raised again by +anything but a person restoring it by hand. Undeclaring the private network does not bring it back. + ## The bus is NATS *2026-09-23, [ADR 0106](../../02-DECISIONS/0106-the-bus-is-nats.md). Architecture to be written -- 2.54.0 From 63c19456b4b1d9d8779ddd39972b66a4fdd4fc76 Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 27 Sep 2026 00:53:13 +0200 Subject: [PATCH 3/4] 0119 review: rollback needs the private network unassigned first; a configuration written back is retired again with the first original kept; only the interface's own file, never a link --- .../0119-a-taken-tunnels-predecessor-is-retired.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/02-DECISIONS/0119-a-taken-tunnels-predecessor-is-retired.md b/02-DECISIONS/0119-a-taken-tunnels-predecessor-is-retired.md index 89db5b1..40f8f33 100644 --- a/02-DECISIONS/0119-a-taken-tunnels-predecessor-is-retired.md +++ b/02-DECISIONS/0119-a-taken-tunnels-predecessor-is-retired.md @@ -64,8 +64,17 @@ is removed from where its unit reads it.** 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: copy the kept original back - and start its unit. The mesh does neither. +- 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/.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. -- 2.54.0 From 0042ca9258d9a544a53fb3c11aef9c61b5adf34c Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 27 Sep 2026 00:58:55 +0200 Subject: [PATCH 4/4] 0119: link ADR 0118 now that it is on main --- 02-DECISIONS/0119-a-taken-tunnels-predecessor-is-retired.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/02-DECISIONS/0119-a-taken-tunnels-predecessor-is-retired.md b/02-DECISIONS/0119-a-taken-tunnels-predecessor-is-retired.md index 40f8f33..d9bdade 100644 --- a/02-DECISIONS/0119-a-taken-tunnels-predecessor-is-retired.md +++ b/02-DECISIONS/0119-a-taken-tunnels-predecessor-is-retired.md @@ -53,7 +53,7 @@ is removed from where its unit reads it.** 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 0118, in review). A machine whose + ([ADR 0118](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. @@ -83,5 +83,5 @@ is removed from where its unit reads it.** - [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 0118 (in review): nothing is started on the way out +- [ADR 0118](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` -- 2.54.0