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)
|
- **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)
|
- **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)
|
- **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
|
### Its tiers, from the bottom up
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ code:
|
|||||||
- mesh-host internal/apply (the service that reflects a rule set)
|
- mesh-host internal/apply (the service that reflects a rule set)
|
||||||
updated: 2026-09-22
|
updated: 2026-09-22
|
||||||
decisions:
|
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/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/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
|
- 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
|
rule. *A rule set that is written but never loaded passes every check that reads the file, which
|
||||||
is why the check reads packets.*
|
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
|
### 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
|
*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
|
opened: 2026-09-22
|
||||||
located-in: []
|
located-in: [mesh-catalog, mesh-controller internal/catalogue]
|
||||||
fixed-by:
|
fixed-by:
|
||||||
amended-design:
|
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.
|
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
|
- Is there a general rule here for "a requirement whose only provider owns a scarce port", or is
|
||||||
each such provision its own decision?
|
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