Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6b638e63c |
@@ -137,16 +137,18 @@ its name to everyone would have published exactly what `internal` was chosen to
|
|||||||
|
|
||||||
The earlier insight above says the port is not the path for a routed endpoint. This is its other
|
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
|
half: the proxy is the path, so the proxy is where `internal` is enforced. It serves an internal name
|
||||||
only to a request from the private network — the mesh's range, the machine itself, or one of its own
|
only to the machines of the mesh and to the machine itself
|
||||||
container networks ([ADR 0144](0144-anything-on-a-machine-may-call-anything-on-it.md)). To anyone else, the name is
|
([ADR 0144](0144-anything-on-a-machine-may-call-anything-on-it.md)). Who the mesh is, it is told,
|
||||||
answered as one never routed, in the handshake and in the request, and not listed among the names it
|
not left to work out: its membership carries the same list of machine addresses the filter's "from the
|
||||||
serves. This holds for the internal name of a `both` endpoint too, whose outsiders have its public
|
mesh" is rendered from ([ADR 0167](0167-a-membership-carries-what-its-module-receives-and-who-the-mesh-is.md)).
|
||||||
name.
|
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
|
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 the mesh
|
derived from it. Checked in the proxy's own tests: an internal-only name is served to a machine the
|
||||||
range, to loopback and to a container bridge, and refused, unlisted and uncertified for a request
|
membership names and to loopback, and refused, unlisted and uncertified for any other request; until
|
||||||
from outside; with no range given, it is served to the machine alone.
|
the mesh is issued, it is served to the machine alone.
|
||||||
|
|
||||||
## Consequences
|
## Consequences
|
||||||
|
|
||||||
|
|||||||
+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
|
||||||
@@ -177,6 +177,7 @@ 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)
|
- **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)
|
- **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)
|
- **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)
|
||||||
|
|
||||||
### 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-10-02
|
updated: 2026-10-02
|
||||||
decisions:
|
decisions:
|
||||||
|
- 02-DECISIONS/0167-a-membership-carries-what-its-module-receives-and-who-the-mesh-is.md
|
||||||
- 02-DECISIONS/0148-the-meshs-names-are-resolved-not-copied-into-containers.md
|
- 02-DECISIONS/0148-the-meshs-names-are-resolved-not-copied-into-containers.md
|
||||||
- 02-DECISIONS/0151-a-routes-internal-name-is-composed-under-the-node-that-serves-it.md
|
- 02-DECISIONS/0151-a-routes-internal-name-is-composed-under-the-node-that-serves-it.md
|
||||||
- 02-DECISIONS/0147-a-module-anchors-the-meshs-authority.md
|
- 02-DECISIONS/0147-a-module-anchors-the-meshs-authority.md
|
||||||
@@ -846,10 +847,13 @@ One value, three readers:
|
|||||||
([ADR 0138](../../02-DECISIONS/0138-an-assignment-binds-an-endpoint-and-says-how-far-it-reaches.md),
|
([ADR 0138](../../02-DECISIONS/0138-an-assignment-binds-an-endpoint-and-says-how-far-it-reaches.md),
|
||||||
its insight of this date). It answers public and internal names on the same listeners, so the name a
|
its insight of this date). It answers public and internal names on the same listeners, so the name a
|
||||||
request carries is the request's own claim, not where the request came from. An internal name is
|
request carries is the request's own claim, not where the request came from. An internal name is
|
||||||
served to the mesh's range, to the machine itself and to its own container networks; to anyone else
|
served to the machines of the mesh and to the machine itself; to anyone else it is answered as a name
|
||||||
it is answered as a name never routed, in the handshake as well as the request. Without this, an
|
never routed, in the handshake as well as the request. Without this, an endpoint with reach `internal`
|
||||||
endpoint with reach `internal` would be public under a name that is easy to guess
|
would be public under a name that is easy to guess
|
||||||
([issue 191](../../04-ISSUES/191-a-route-with-only-an-internal-name-is-dropped/00-report.md)).
|
([issue 191](../../04-ISSUES/191-a-route-with-only-an-internal-name-is-dropped/00-report.md)).
|
||||||
|
The proxy is told who the mesh is, and its routes, in its membership on the bus — the same machine
|
||||||
|
addresses the filter's "from the mesh" is rendered from
|
||||||
|
([ADR 0167](../../02-DECISIONS/0167-a-membership-carries-what-its-module-receives-and-who-the-mesh-is.md)).
|
||||||
|
|
||||||
**An endpoint that is not routed is reached and never named.** No route contribution means no name is
|
**An endpoint that is not routed is reached and never named.** No route contribution means no name is
|
||||||
composed and no certificate requested, while the filter still acts on it. That is the case the model
|
composed and no certificate requested, while the filter still acts on it. That is the case the model
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ code:
|
|||||||
- mesh-tools src/broker-amqp.ts (to be replaced)
|
- mesh-tools src/broker-amqp.ts (to be replaced)
|
||||||
- mesh-catalog modules/nats (to be written)
|
- mesh-catalog modules/nats (to be written)
|
||||||
- mesh-sdk src (the protocol's NATS binding, step 3)
|
- mesh-sdk src (the protocol's NATS binding, step 3)
|
||||||
updated: 2026-10-01
|
updated: 2026-10-02
|
||||||
decisions:
|
decisions:
|
||||||
|
- 02-DECISIONS/0167-a-membership-carries-what-its-module-receives-and-who-the-mesh-is.md
|
||||||
- 02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md
|
- 02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md
|
||||||
- 02-DECISIONS/0157-a-build-says-what-it-does-on-the-bus-as-it-happens.md
|
- 02-DECISIONS/0157-a-build-says-what-it-does-on-the-bus-as-it-happens.md
|
||||||
- 02-DECISIONS/0106-the-bus-is-nats.md
|
- 02-DECISIONS/0106-the-bus-is-nats.md
|
||||||
@@ -94,6 +95,12 @@ list; the account's grant is the same membership read the other way; the console
|
|||||||
tool's subject. The one rule a runtime keeps is the membership's own subject, from the two names in its
|
tool's subject. The one rule a runtime keeps is the membership's own subject, from the two names in its
|
||||||
credential.
|
credential.
|
||||||
|
|
||||||
|
**Revised 2026-10-02** ([ADR 0167](../../02-DECISIONS/0167-a-membership-carries-what-its-module-receives-and-who-the-mesh-is.md)):
|
||||||
|
a membership also carries **what its module receives**, by requirement — the contributions its
|
||||||
|
received file is written from, from the same composition — and **who the mesh is**, every machine's
|
||||||
|
address on the private network, the list the filter's "from the mesh" is rendered from. The route
|
||||||
|
proxy is the first reader of both.
|
||||||
|
|
||||||
**Revised 2026-09-27** ([ADR 0129](../../02-DECISIONS/0129-a-seat-carries-the-protocol-of-its-role.md)):
|
**Revised 2026-09-27** ([ADR 0129](../../02-DECISIONS/0129-a-seat-carries-the-protocol-of-its-role.md)):
|
||||||
**`mesh.build.request`, `mesh.control.built` and the BUILDS stream are gone.** A build is work submitted to a role, and the
|
**`mesh.build.request`, `mesh.control.built` and the BUILDS stream are gone.** A build is work submitted to a role, and the
|
||||||
mesh already has a shape for that — a seat's `accept` subjects, on a work queue with a queue group of
|
mesh already has a shape for that — a seat's `accept` subjects, on a work queue with a queue group of
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
---
|
---
|
||||||
status: located
|
status: located
|
||||||
opened: 2026-10-01
|
opened: 2026-10-01
|
||||||
located-in: [mesh-controller examples/route-proxy/main.go (routesFrom requires a route's public `name` and treats `internal-name` only as an alias of it; the handler serves every routed name to any source), mesh-catalog modules/route-proxy/module.json (the proxy is not told the private network's range)]
|
located-in: [mesh-controller examples/route-proxy/main.go (routesFrom requires a route's public `name` and treats `internal-name` only as an alias of it; the handler serves every routed name to any source), mesh-controller internal/broker/membership.go (a membership says nothing of what its module receives or who the mesh is)]
|
||||||
fixed-by:
|
fixed-by:
|
||||||
amended-design: [03-DESIGN/01-to-be/08-connectivity.md]
|
amended-design: [03-DESIGN/01-to-be/08-connectivity.md, 03-DESIGN/01-to-be/25-the-bus-on-nats.md]
|
||||||
---
|
---
|
||||||
|
|
||||||
# 191 — A route with only an internal name is dropped as naming nothing
|
# 191 — A route with only an internal name is dropped as naming nothing
|
||||||
|
|||||||
@@ -47,19 +47,29 @@ nothing the public one did not. It is a gap in the decision's wording, not only
|
|||||||
0138 says the proxy *serves* the internal name without saying to whom — so it is recorded there as a
|
0138 says the proxy *serves* the internal name without saying to whom — so it is recorded there as a
|
||||||
progressive insight and in the to-be connectivity design.
|
progressive insight and in the to-be connectivity design.
|
||||||
|
|
||||||
**Where "inside" is decided.** The mesh's guard recognises the private network by the interface a
|
**Where "inside" is decided: told, not worked out.** The first correction had the proxy work it out
|
||||||
packet arrives on, never by source address, because a source can be claimed. The proxy cannot see the
|
for itself — the mesh's range from an environment variable the catalogue wrote, and the machine's
|
||||||
interface, so it reads the source: the mesh's range, loopback, and the ranges of the machine's own
|
container bridges from its own interfaces. That was a second definition of "the mesh", kept by one
|
||||||
container bridges, which are the same interfaces the guard names. A claimed source does not carry
|
module beside the one the controller already has: it resolves "from the mesh" to every machine's
|
||||||
here, as a connection needs its replies, and replies to those addresses leave by the tunnel or a
|
address on the private network, and the packet filter is rendered from that list. Reviewed, it was
|
||||||
local bridge. The range reaches the proxy from the catalog as the machine's `mesh-range`, the way the
|
replaced: [ADR 0167](../../02-DECISIONS/0167-a-membership-carries-what-its-module-receives-and-who-the-mesh-is.md)
|
||||||
intrusion filter already receives it. With none given, the proxy serves internal names to the machine
|
has every membership on the bus carry what its module receives and that list, and the proxy follows
|
||||||
alone: refused, not opened.
|
its membership. One composition, read by the filter and by the proxy.
|
||||||
|
|
||||||
**What changed with it.** The internal name of a route that also has a public one is now served to
|
The proxy reads the source address, where the guard reads the interface, because it cannot see the
|
||||||
the private network only, like any other internal name. Outsiders have the public name, so nothing
|
interface a request arrived on. A claimed source does not carry here: a connection needs its replies,
|
||||||
they could reach is lost.
|
and replies to a mesh address leave by the tunnel.
|
||||||
|
|
||||||
**Order of release.** The catalog change comes first. A proxy built from the change but started
|
**What changed with it.** The internal name of a route that also has a public one is now served to the
|
||||||
without the range would serve internal names to its own machine alone, and every other member of the
|
mesh only, like any other internal name. Outsiders have the public name, so nothing they could reach is
|
||||||
mesh would lose them until the range arrived.
|
lost. A container calling its own machine's internal name arrives from its container network and is
|
||||||
|
refused; whether the mesh should issue those networks too is left open in ADR 0167.
|
||||||
|
|
||||||
|
**Order of release.**
|
||||||
|
|
||||||
|
1. The catalogue change, which gives the proxy a bus account. A machine running the proxy is not
|
||||||
|
composed until its account is issued, so the account is issued straight after
|
||||||
|
(`module issue route-proxy --node <machine>`), and then the machine is pushed.
|
||||||
|
2. The controller and proxy change. The push after it publishes memberships that carry the routes and
|
||||||
|
the mesh, and each proxy takes them. Until then, a proxy serves its file, and internal names to its
|
||||||
|
own machine alone.
|
||||||
|
|||||||
Reference in New Issue
Block a user