ADR 0104: a provision may be answered by an adapter to the predecessor; issue 093 located; connectivity says how the proxy hands over
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
---
|
||||
topic: the mesh
|
||||
status: accepted
|
||||
date: 2026-09-22
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
extends: 02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md
|
||||
---
|
||||
|
||||
# 104. A provision may be answered by an adapter to the predecessor
|
||||
|
||||
## Context
|
||||
|
||||
[Issue 093](../04-ISSUES/093-the-successor-proxy-cannot-serve-what-the-predecessor-still-serves/00-report.md),
|
||||
found on the first adopted node hours after it was raised. Every module reachable by name requires
|
||||
a **route**. The mesh has one provider of it, its own proxy, which binds the two public ports on
|
||||
the machine's own network. The predecessor's proxy holds those ports and serves every public name
|
||||
there. So:
|
||||
|
||||
- a web module cannot be taken until the mesh's proxy runs;
|
||||
- the mesh's proxy cannot run until the predecessor's stops;
|
||||
- and when it stops, every name the predecessor served goes dark, because the mesh's proxy serves
|
||||
only what mesh modules have contributed.
|
||||
|
||||
Adoption ([ADR 0100](0100-a-node-in-use-is-adopted-before-it-is-converged.md)) keeps a *file* as it
|
||||
was and a *container* as it was. It has nothing for a service whose successor is **different
|
||||
software**: nothing shares a name to hold, so there is only replacement, of every name at once.
|
||||
|
||||
The certificates decide the rest. The predecessor's proxy holds them and renews them. A successor
|
||||
taking over first would have to obtain a certificate for every public name in the same window in
|
||||
which it took the ports — with nothing else migrated yet, and the only way back being to start the
|
||||
predecessor again.
|
||||
|
||||
## Considered Options
|
||||
|
||||
1. **The successor proxy serves routes it did not derive**, given as the operator's configuration,
|
||||
each disappearing as the module owning that name contributes its own. Rejected for the
|
||||
migration: it puts the riskiest step first, and it puts configuration the mesh does not own
|
||||
into the one component whose design is that the mesh derives its configuration.
|
||||
2. **Take the proxy first and accept the window.** Rejected: every public name at once, before
|
||||
anything else has moved, with certificates to obtain in the same window.
|
||||
3. **Make the requirement optional**, so a module can be taken while nothing provides route.
|
||||
Rejected: it is not optional. A module that says it must be reachable and is not is a module
|
||||
reporting success while serving nobody.
|
||||
4. **An adapter module provides the provision by writing into the predecessor's service.** Adopted.
|
||||
|
||||
## Decision
|
||||
|
||||
**A provision whose only provider owns a scarce machine-wide resource may be answered, during a
|
||||
migration, by an adapter module that writes into the predecessor's own configuration.**
|
||||
|
||||
For the route: a module that **provides `route`**, receives every contribution as the proxy does,
|
||||
and writes each one as a route file where the predecessor's proxy reads its configuration —
|
||||
pointing at the machine port the contributing module now publishes. The predecessor's proxy keeps
|
||||
serving every name it already serves, keeps its certificates and keeps renewing them; a name whose
|
||||
module has migrated is served by the same proxy, pointing at the mesh's container instead of the
|
||||
predecessor's.
|
||||
|
||||
**It is migration scaffolding and says so.** It is assigned only on an adopted node, it writes only
|
||||
files it can name as its own, and it is removed when the predecessor's proxy retires — at which
|
||||
point the mesh's own proxy takes the ports and already knows every route, because by then every
|
||||
name belongs to a module that contributes it.
|
||||
|
||||
**This does not make the mesh the predecessor's manager.** The adapter writes route files and
|
||||
nothing else, and only because the predecessor's control has been stopped by the operator, which
|
||||
ADR 0100 requires before a node is adopted.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The migration keeps its shape: one service at a time, each reversible on its own. The proxy is
|
||||
the **last** cutover again, not the first.
|
||||
- There is no certificate event until the end, and at the end there is one, for a proxy that
|
||||
already has every route from the mesh.
|
||||
- The mesh writes into a directory the predecessor owns. That is safe only while the predecessor's
|
||||
control is stopped, and it is why the adapter is refused on a converged node.
|
||||
- The adapter is throwaway code with a stated end. Something has to delete it; that something is
|
||||
the operator, and the flip is the moment.
|
||||
- A second provision with this shape — a resolver on 53, a mail relay on 25 — now has a pattern to
|
||||
follow rather than a new argument to have.
|
||||
|
||||
## How it is checked
|
||||
|
||||
Unit tests hold the adapter to writing one route file per contribution, naming each file as its
|
||||
own, removing a file when its contribution goes, and leaving every file it did not write. The
|
||||
controller is held to resolving `route` from it exactly as from the proxy. The adoption lab bed
|
||||
gains a step: with the predecessor's proxy serving a name, a module taken behind the adapter is
|
||||
reachable under that name, and the predecessor's own names keep answering. Assigning the adapter
|
||||
to a converged node is refused.
|
||||
|
||||
## References
|
||||
|
||||
- [Issue 093](../04-ISSUES/093-the-successor-proxy-cannot-serve-what-the-predecessor-still-serves/00-report.md)
|
||||
- [ADR 0100](0100-a-node-in-use-is-adopted-before-it-is-converged.md), [ADR 0007](0007-connectivity.md)
|
||||
@@ -92,6 +92,7 @@ python3 00-META/checks/index.py fail if stale
|
||||
- **0101** — [A machine's own resolver does not make it in use](0101-a-machines-own-resolver-does-not-make-it-in-use.md)
|
||||
- **0102** — [The mesh writes into a shared file, never over it](0102-the-mesh-writes-into-a-shared-file-never-over-it.md)
|
||||
- **0103** — [What an adopted node holds, and what its guard refuses](0103-what-an-adopted-node-holds-and-what-its-guard-refuses.md)
|
||||
- **0104** — [A provision may be answered by an adapter to the predecessor](0104-a-provision-may-be-answered-by-an-adapter-to-the-predecessor.md)
|
||||
|
||||
### Its tiers, from the bottom up
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ code:
|
||||
- mesh-host internal/apply (the service that reflects a rule set)
|
||||
updated: 2026-09-22
|
||||
decisions:
|
||||
- 02-DECISIONS/0104-a-provision-may-be-answered-by-an-adapter-to-the-predecessor.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
|
||||
- 02-DECISIONS/0099-a-step-that-runs-once-names-what-it-reads.md
|
||||
@@ -535,6 +536,19 @@ undeclared one does not — then the module is removed and the port closes with
|
||||
rule. *A rule set that is written but never loaded passes every check that reads the file, which
|
||||
is why the check reads packets.*
|
||||
|
||||
### While a predecessor still serves the same thing
|
||||
|
||||
*2026-09-22, [ADR 0104](../../02-DECISIONS/0104-a-provision-may-be-answered-by-an-adapter-to-the-predecessor.md).*
|
||||
The mesh's proxy owns the two public ports and derives its routes from what modules contributed, so
|
||||
it cannot stand beside a predecessor's proxy and cannot serve the predecessor's names. During a
|
||||
migration a provision of this shape — one provider, a scarce machine-wide resource — may instead be
|
||||
answered by an **adapter**: a module that provides `route` and writes each contribution into the
|
||||
predecessor's own configuration, pointing at the machine port the contributing module publishes.
|
||||
The predecessor's proxy goes on serving every name and renewing every certificate; the mesh's proxy
|
||||
is taken last, when every route is one the mesh contributed. The adapter is refused on a converged
|
||||
node, and removed when the predecessor's proxy retires. *How it is checked:* the adoption bed takes
|
||||
a module behind the adapter and asserts the name answers and the predecessor's own names still do.
|
||||
|
||||
### On an adopted node
|
||||
|
||||
*2026-09-22, [ADR 0100](../../02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md).* **The firewall found on the machine stays in force**, and the mesh
|
||||
|
||||
+11
-2
@@ -1,7 +1,7 @@
|
||||
---
|
||||
status: open
|
||||
status: located
|
||||
opened: 2026-09-22
|
||||
located-in: []
|
||||
located-in: [mesh-catalog, mesh-controller internal/catalogue]
|
||||
fixed-by:
|
||||
amended-design:
|
||||
---
|
||||
@@ -62,3 +62,12 @@ It has to be first, because everything reachable by name waits on it.
|
||||
otherwise ask a public authority for every name at the moment of the cutover.
|
||||
- Is there a general rule here for "a requirement whose only provider owns a scarce port", or is
|
||||
each such provision its own decision?
|
||||
|
||||
## Decided
|
||||
|
||||
*2026-09-22.* [ADR 0104](../../02-DECISIONS/0104-a-provision-may-be-answered-by-an-adapter-to-the-predecessor.md):
|
||||
during a migration, a provision whose only provider owns a scarce machine-wide resource may be
|
||||
answered by an **adapter** that writes into the predecessor's own configuration. For the route,
|
||||
the predecessor's proxy keeps serving every name and keeps its certificates, while each migrated
|
||||
module's name is pointed at the mesh's container. The proxy is the last cutover again, and by then
|
||||
every route is one the mesh contributed.
|
||||
|
||||
Reference in New Issue
Block a user