Merge remote-tracking branch 'origin/main' into decision/docker-module
This commit is contained in:
@@ -124,6 +124,32 @@ This corrects a fact, not the decision: one statement per endpoint, three things
|
||||
none of them deciding on its own, all stand. The table in the decision should be read with the filter
|
||||
column applying to an unrouted endpoint.
|
||||
|
||||
## Progressive insight — 2026-10-02, from issue 191
|
||||
|
||||
**For a routed endpoint, "the proxy serves the internal name" has to mean "serves it to the private
|
||||
network", and only the proxy can make it mean that.** The decision says `internal` means the proxy
|
||||
serves the internal name and not the public one. It does not say to whom, and the proxy answered
|
||||
every name it routes to any request that carried it, on the same listeners as its public names. A
|
||||
name being internal kept nobody out: a request from the internet only had to send it. While every
|
||||
routed endpoint also had a public name, nothing showed it. Once an endpoint could be internal alone
|
||||
([issue 191](../04-ISSUES/191-a-route-with-only-an-internal-name-is-dropped/00-report.md)), serving
|
||||
its name to everyone would have published exactly what `internal` was chosen to keep private.
|
||||
|
||||
The earlier insight above says the port is not the path for a routed endpoint. This is its other
|
||||
half: the proxy is the path, so the proxy is where `internal` is enforced. It serves an internal name
|
||||
only to the machines of the mesh and to the machine itself
|
||||
([ADR 0144](0144-anything-on-a-machine-may-call-anything-on-it.md)). Who the mesh is, it is told,
|
||||
not left to work out: its membership carries the same list of machine addresses the filter's "from the
|
||||
mesh" is rendered from ([ADR 0167](0167-a-membership-carries-what-its-module-receives-and-who-the-mesh-is.md)).
|
||||
To anyone else, the name is answered as one never routed, in the handshake and in the request, and not
|
||||
listed among the names it serves. This holds for the internal name of a `both` endpoint too, whose
|
||||
outsiders have its public name.
|
||||
|
||||
The decision, the options and the consequences stand: one statement per endpoint, three things
|
||||
derived from it. Checked in the proxy's own tests: an internal-only name is served to a machine the
|
||||
membership names and to loopback, and refused, unlisted and uncertified for any other request; until
|
||||
the mesh is issued, it is served to the machine alone.
|
||||
|
||||
## Consequences
|
||||
|
||||
- **A manifest gains endpoint names, and a route contribution names an endpoint instead of a port.**
|
||||
|
||||
@@ -131,6 +131,32 @@ the plan says it too.
|
||||
| Genesis raises the forge as its module declares it | a genesis test that raises, assigns, and finds the module holding rather than raising a second |
|
||||
| Live | the next cutover on an adopted machine: `take` shows the comparison, refuses the downgrade if there is one, and the service keeps its configuration and its secret |
|
||||
|
||||
## Built, 2026-10-02
|
||||
|
||||
> **Progressive insight — 2026-10-02.** The decision stands; these are the facts of its building.
|
||||
|
||||
Built across mesh-host 63 and 64 and mesh-controller 201, 202 and the pull request that followed
|
||||
them. Rule 1: `take` previews every held thing's comparison and ends with a digest; `take --yes
|
||||
<digest>` acts on exactly that preview, and a changed preview or an account older than the flip
|
||||
allows is refused, as the flip's are. A published port's reach is said as the machine reported it,
|
||||
behind the found firewall whose rules are not read. Rule 2: an older image, a differing file and a
|
||||
minted, unaccepted secret for found data refuse, overridden by `--downgrade`, `--replace <path>` and
|
||||
`--mint <name>`; the secrets a module holds on a machine are read with where each came from. Rule 3:
|
||||
`secret accept --provider` reaches a required secret. Rule 4: the per-machine setting is `networks`,
|
||||
a container id to the found networks it keeps; judged for an adopted machine only, joined by the host
|
||||
after the container runs, part of the container's spec, named in the preview. Rule 5: the host's
|
||||
facts, former targets and strays. Rule 6: one judgement, run where a setting is stored and where a
|
||||
machine is composed; a module whose stored setting its definition can no longer compose is left out
|
||||
of the declaration, the envelope says so, the host keeps that module's things, and `plan` and `push`
|
||||
say it by name. A key that reaches nothing is refused where stored and said by `plan`, and never
|
||||
costs a module. Rule 7: genesis raises the forge under the module's container name, with its image
|
||||
digest and its data directory; the network is the one difference left, said by the take, because the
|
||||
bootstrap forge reaches the store on the machine's loopback.
|
||||
|
||||
**Not yet proven live.** Every machine of this mesh is converged, so the table's last row — a take
|
||||
on an adopted machine — waits for the next adoption. What is live is what the rows above it check.
|
||||
Issues 086, 098, 099, 100 and 101 stay located until that row is read.
|
||||
|
||||
## References
|
||||
|
||||
- [ADR 0100](0100-a-node-in-use-is-adopted-before-it-is-converged.md), [ADR 0102](0102-the-mesh-writes-into-a-shared-file-never-over-it.md), [ADR 0103](0103-what-an-adopted-node-holds-and-what-its-guard-refuses.md), [ADR 0104](0104-a-provision-may-be-answered-by-an-adapter-to-the-predecessor.md), [ADR 0162](0162-a-merge-produces-a-tiered-plan-the-mesh-keeps.md)
|
||||
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
---
|
||||
topic: the mesh
|
||||
status: accepted
|
||||
date: 2026-10-02
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
extends: 02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md
|
||||
---
|
||||
|
||||
# 167. A membership carries what its module receives, and who the mesh is
|
||||
|
||||
## Context
|
||||
|
||||
A provider learns what it is given from a file. The controller composes every consumer's contribution
|
||||
to a requirement, and the node's declaration writes them into the provider's received file. The route
|
||||
proxy reads its routes that way: one JSON file, re-read every two seconds.
|
||||
|
||||
[Issue 191](../04-ISSUES/191-a-route-with-only-an-internal-name-is-dropped/00-report.md) showed what
|
||||
that file leaves out. Since [ADR 0138](0138-an-assignment-binds-an-endpoint-and-says-how-far-it-reaches.md),
|
||||
a route whose endpoint reaches only the private network carries an internal name and no public one.
|
||||
The proxy dropped it. Serving it was not enough either: the proxy answers public and internal names on
|
||||
the same listeners, so an internal name served to every request is public under a guessable name. To
|
||||
serve it correctly the proxy needs a second fact, **who the mesh is**, and nothing gave it one.
|
||||
|
||||
The first attempt had the proxy work it out: the mesh's range from an environment variable written by
|
||||
the catalogue, and the machine's container bridges read from its own interfaces. That is a second
|
||||
definition of "the mesh", kept by one module, beside the one the packet filter already uses. The
|
||||
controller resolves "from the mesh" to every machine's address on the private network, and the filter
|
||||
is rendered from that list. Two definitions agree until one changes.
|
||||
|
||||
[ADR 0160](0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md)
|
||||
already gives every assignment one document on the bus, its membership, read once at connect and
|
||||
followed. It says what the assignment serves and reaches. It does not yet say what it is given.
|
||||
|
||||
## Considered Options
|
||||
|
||||
1. **Keep the file, add the mesh to it.** The proxy keeps polling a file, and the controller writes the
|
||||
mesh's addresses beside the routes. It fixes the definition, but delivery stays a file re-read on a
|
||||
timer, written by a separate path from the one every other fact a module is told now takes.
|
||||
2. **Have the proxy work it out** from a range in its environment and the machine's interfaces. Rejected:
|
||||
it is the second definition this record exists to remove.
|
||||
3. **The membership carries it.** What each module receives, from the same composition its received
|
||||
file is written from, and the mesh's addresses, from the same list the filter is rendered from. The
|
||||
proxy follows its membership and serves exactly that.
|
||||
|
||||
## Decision
|
||||
|
||||
**Option 3.**
|
||||
|
||||
- **A membership carries what its module receives**, by requirement: the contributions every consumer
|
||||
made, exactly as composed for its received file. A requirement nobody contributed to is an empty
|
||||
list, never absent, for the reason the file is written empty: "nothing asked" and "never told" want
|
||||
different responses.
|
||||
- **A membership carries who the mesh is**: every machine's address on the private network, the list
|
||||
a rule saying "from the mesh" resolves to. One list, two readers: the filter and any module that
|
||||
must tell the mesh from the world.
|
||||
- **The route proxy reads its routes and the mesh from its membership**, with the bus account every
|
||||
module that speaks on the bus is given. It serves an internal name only to the machines the mesh
|
||||
names and to the machine itself, and answers anyone else as it answers a name it never routed: in
|
||||
the request, in the handshake, and in the list of names it serves.
|
||||
- **The file stays until the bus has spoken.** While a proxy has read no membership that carries routes,
|
||||
it serves the file, and an internal name only to its own machine: refused, never opened. A
|
||||
membership from a controller that issues no routes changes nothing.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Every membership grows two fields. A machine joining or leaving republishes every membership, which
|
||||
a push already does.
|
||||
- A provider that receives something is told it twice for now, in its file and on the bus. The file
|
||||
goes when every provider reads its membership; that is its own change.
|
||||
- The route proxy needs a bus account. It is issued like any module's, so a machine running the proxy
|
||||
cannot be composed between the catalogue declaring the account and the operator issuing it. The
|
||||
machine keeps what it runs meanwhile.
|
||||
- The internal name of a route that also has a public one is now served to the mesh only. Outsiders
|
||||
have the public name.
|
||||
- A container on the same machine that calls that machine's own internal name arrives from its
|
||||
container network, not from a mesh address, and is refused. Calls between machines are unaffected:
|
||||
they leave by the machine's mesh address. Whether the mesh should also issue each machine's container
|
||||
networks is left open, because the mesh does not record them today.
|
||||
|
||||
## How this is checked
|
||||
|
||||
| Rule | Checked by |
|
||||
|---|---|
|
||||
| What a provider receives on the bus is what its received file says, same-node port fix included | a controller test composing a provider and a consumer on one machine and comparing the two |
|
||||
| An internal name is served to the machines the membership names and to loopback, and to nobody else | the proxy's tests: served from a named address and from loopback; refused, unlisted and uncertified from any other |
|
||||
| A membership that carries no routes, or a mesh that cannot be read, changes nothing | the proxy's tests |
|
||||
| Until the mesh is issued, an internal name is served to the machine alone | the proxy's tests |
|
||||
| Live: an internal-only route answers over the mesh and is refused from outside | by hand, after the release |
|
||||
|
||||
## References
|
||||
|
||||
- [ADR 0160](0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md) —
|
||||
the membership this extends
|
||||
- [ADR 0138](0138-an-assignment-binds-an-endpoint-and-says-how-far-it-reaches.md) — reach, and the
|
||||
insight of 2026-10-02 that the proxy is where internal reach is kept
|
||||
- [ADR 0144](0144-anything-on-a-machine-may-call-anything-on-it.md) — the machine itself is always inside
|
||||
- [Issue 191](../04-ISSUES/191-a-route-with-only-an-internal-name-is-dropped/00-report.md) — what
|
||||
found it
|
||||
@@ -0,0 +1,120 @@
|
||||
---
|
||||
topic: the mesh
|
||||
status: accepted
|
||||
date: 2026-10-02
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
extends: 02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md
|
||||
---
|
||||
|
||||
# 168. A converged machine is filtered by the mesh alone, and the host says what else refuses
|
||||
|
||||
## Context
|
||||
|
||||
[ADR 0100](0100-a-node-in-use-is-adopted-before-it-is-converged.md) says what converging does to
|
||||
the firewall a machine was found with: the mesh's derived filter is loaded in place of the
|
||||
refusal-only guard, and the found firewall is retired — disabled, never flushed. Four issues from
|
||||
the first two convergences are four ways that sentence was not the machine:
|
||||
|
||||
- the flip reported the found firewall retired and it was active two minutes later; fifty minutes
|
||||
on, a reconcile found it disabled by hand and recorded that the mesh had done it
|
||||
([143](../04-ISSUES/143-converging-does-not-retire-the-firewall-it-found/00-report.md));
|
||||
- "the firewall found" named one front end, and what filtered the forwarded path on that machine
|
||||
was a chain a predecessor had installed in the container runtime's user hook — invisible to the
|
||||
mesh, refusing two ports the mesh declared open, and when it was removed, carrying an allowance
|
||||
every module reaching another by the machine's own name had been relying on
|
||||
([144](../04-ISSUES/144-the-predecessors-rules-outlive-the-firewall-it-was-found-as/00-report.md),
|
||||
[145](../04-ISSUES/145-a-machine-reads-healthy-while-its-modules-cannot-reach-each-other/00-report.md));
|
||||
- the forward chain listed address ranges that followed neither the modules nor the machine
|
||||
([141](../04-ISSUES/141-the-forward-chain-does-not-follow-the-modules/00-report.md)), answered by
|
||||
[ADR 0140](0140-the-filter-constrains-what-arrives-from-outside.md) before this record;
|
||||
- the networking module wrote two machine-wide files whole, so taking it restarted every
|
||||
container ([084](../04-ISSUES/084-taking-networking-on-an-adopted-node-restarts-every-container/00-report.md)),
|
||||
answered by [ADR 0102](0102-the-mesh-writes-into-a-shared-file-never-over-it.md) and the hosts
|
||||
file's marked region ([issue 128](../04-ISSUES/128-the-hosts-file-is-written-whole/00-report.md)).
|
||||
|
||||
Read on the four machines of this mesh on 2026-10-02, after every one had converged: on both
|
||||
machines that had a front end it is inactive, and the host's record says the mesh retired it on
|
||||
both — true of one, false of the other. On the home server the predecessor's chain is still in
|
||||
force on the forwarded path, in the legacy packet filter the mesh's reader of rules does not
|
||||
consult once a machine is converged, so that machine is filtered by two things and the mesh says
|
||||
one. The host's reader already knows how to tell a table that refuses traffic from the runtime's
|
||||
own plumbing and from a ban list; it is asked once, at adoption, and only to refuse a machine
|
||||
whose firewall nobody speaks. Nothing asks it afterwards, and nothing reports what it saw.
|
||||
|
||||
The group's exit is one sentence: *a converged machine has exactly one thing filtering it, and
|
||||
the mesh says truthfully which.* The first half the mesh can enforce only for what it owns; the
|
||||
second half it can always do, and it is the half that was missing.
|
||||
|
||||
## Decision
|
||||
|
||||
**1. Convergence is a state the host keeps, not a step it takes once.** Every apply of a converged
|
||||
declaration reads whether the found firewall is in force. Active — enabled again by a package, a
|
||||
boot, a hand — it is retired again and said. The record distinguishes *the mesh disabled it* from
|
||||
*it was found inactive*, and a reconcile that finds it inactive never records that the mesh did
|
||||
it. When the step is skipped because the apply had failures, the report says the found firewall
|
||||
was left in force and why; a step that does nothing is never silent.
|
||||
|
||||
**2. The host reports what filters the machine, with every apply, adopted or converged.** Every
|
||||
table of the packet filter, and every chain of the legacy filter, that refuses traffic — a drop or
|
||||
a reject, or a base chain whose policy drops — with an owner: the *mesh's*, the *found firewall's*,
|
||||
the *container runtime's own*, a *ban* (a refusal that names the sources it refuses, in a chain
|
||||
that accepts nothing), or *other*. The runtime's own is its plumbing — its chains, the forward
|
||||
policy it sets when it turns forwarding on, its guard against reaching a container's address from
|
||||
off its bridge. The user chain the runtime leaves for an administrator is not the runtime's:
|
||||
anything refusing in it is *other*, which is where both predecessors' chains lived. Each entry
|
||||
says in one line what it refuses. The mesh removes none of it: a rule it did not write is the
|
||||
operator's to remove, now that they can see it.
|
||||
|
||||
**3. The mesh says which.** `node show` lists the filters with their owners. `status` names every
|
||||
converged machine that something other than the mesh's table, the runtime's plumbing and a ban
|
||||
list filters, the way it names strays and untaken modules, and such a machine is not "all well".
|
||||
The converge preview lists the filters found and the fate of each: the found firewall retired, the
|
||||
runtime's and the bans left, *other* left and named — so a person knows before the flip that the
|
||||
machine will not be filtered by the mesh alone until they remove it, and what they would be
|
||||
removing. *A converged machine is filtered by the mesh alone* when its list holds nothing but the
|
||||
mesh's, the runtime's own and bans.
|
||||
|
||||
**4. Adoption's threshold does not move.** A machine whose front end nobody speaks is still refused
|
||||
adoption; a refusing rule in the runtime's user chain still does not refuse it — on both machines
|
||||
of this mesh it would have, and the migration would not have happened. It is reported instead,
|
||||
from the first report on.
|
||||
|
||||
**5. Two of the group's issues are settled by records already accepted.** The forward chain follows
|
||||
the machine's outward links and says nothing about networks ([ADR 0140](0140-the-filter-constrains-what-arrives-from-outside.md)),
|
||||
which answers 141 whole. The runtime's file is written into and reloaded, and the hosts file's
|
||||
region is the mesh's alone ([ADR 0102](0102-the-mesh-writes-into-a-shared-file-never-over-it.md),
|
||||
[issue 128](../04-ISSUES/128-the-hosts-file-is-written-whole/00-report.md)), which answers 084. One
|
||||
machine-wide file the mesh still writes whole is its own filter, at the path the distribution's
|
||||
packet filter reads; an operator's own rules at that path would be contested, and are held as
|
||||
found until the filter module is taken ([ADR 0163](0163-taking-a-module-over-is-a-comparison.md)).
|
||||
That is a difference a take shows, not a fault, and is decided when it bites.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The host's report grows by the filters it found and, for a converged machine, the state of its
|
||||
found firewall and who retired it; the controller keeps both on the node's record.
|
||||
- `retireFirewall` runs on every converged apply and can disable the found firewall more than
|
||||
once; the record's *disabled by the mesh* means exactly that.
|
||||
- The reader of rules gains an owner per table and chain; what it refuses adoption for does not
|
||||
change. A ban stays what it was: not a firewall.
|
||||
- Issues 143 and 144 close on rules 1 to 3 once a machine's record names the predecessor's chain;
|
||||
141 closes on ADR 0140 and 084 on ADR 0102, both by reading.
|
||||
- Removing what is reported is the operator's act, by hand, with the preview's words in front of
|
||||
them. The mesh never flushes and never deletes a rule it did not mark.
|
||||
|
||||
## How this is checked
|
||||
|
||||
| Rule | Checked by |
|
||||
|---|---|
|
||||
| Every refusing table and chain is classified, the user chain's refusals as *other* | host tests over rulesets captured from three machines of this mesh: a predecessor's chain in the legacy filter, a ban list and empty front-end chains beside the runtime's, a virtualisation host and an endpoint agent that refuse nothing |
|
||||
| The found firewall active again on a converged machine is retired again and said; found inactive is recorded as found, not done; a skipped step is said | host tests over a fake front end |
|
||||
| The report carries the filters and the found firewall's state for a converged machine | a host test reading the report |
|
||||
| `node show` lists filters with owners; `status` names a converged machine something else filters and is not well; the preview lists filters and fates | controller tests over a fixture report |
|
||||
| Live | the home server's record names the predecessor's chain in the runtime's user chain as *other*; `status` names the machine; after the operator removes the chain, the next report drops it and `status` is well |
|
||||
|
||||
## References
|
||||
|
||||
- [ADR 0100](0100-a-node-in-use-is-adopted-before-it-is-converged.md), [ADR 0103](0103-what-an-adopted-node-holds-and-what-its-guard-refuses.md), [ADR 0140](0140-the-filter-constrains-what-arrives-from-outside.md), [ADR 0102](0102-the-mesh-writes-into-a-shared-file-never-over-it.md), [ADR 0163](0163-taking-a-module-over-is-a-comparison.md)
|
||||
- [Design 08 — Connectivity](../03-DESIGN/01-to-be/08-connectivity.md), [Design 05 — The node host](../03-DESIGN/01-to-be/05-the-node-host.md)
|
||||
- Issues 084, 141, 143, 144, 145
|
||||
@@ -177,6 +177,8 @@ python3 00-META/checks/index.py fail if stale
|
||||
- **0161** — [What deserves a seat: a role of a module is a seat, a singular fact about machines is a placement with a capacity of one, and a holder's software is the machine's](0161-what-deserves-a-seat.md)
|
||||
- **0162** — [A merge produces a tiered plan the mesh keeps, and a module's dependencies are one relation in the catalogue](0162-a-merge-produces-a-tiered-plan-the-mesh-keeps.md)
|
||||
- **0163** — [Taking a module over is a comparison: what it compares, what it refuses, and what it carries](0163-taking-a-module-over-is-a-comparison.md)
|
||||
- **0167** — [A membership carries what its module receives, and who the mesh is](0167-a-membership-carries-what-its-module-receives-and-who-the-mesh-is.md)
|
||||
- **0168** — [A converged machine is filtered by the mesh alone, and the host says what else refuses](0168-a-converged-machine-is-filtered-by-the-mesh-alone.md)
|
||||
|
||||
### Its tiers, from the bottom up
|
||||
|
||||
|
||||
Reference in New Issue
Block a user