Files
mesh-controller/examples/modules
jschoubben 1f5b70a995 The mesh assigns the port, and a module says it once
novox/hq ADR 0038. A module cannot choose a port: it is written once and
assigned anywhere, so any number it picks is a guess about a machine it
has never seen. A database module met the mesh's own store on 5432 and
was told, by a container runtime three layers down, that the port was
already allocated.

The number used to appear three times in every module — the rule set,
what a consumer is told, and what the runtime publishes — agreeing only
because one person wrote all three. Now it appears once, in `listens`,
and the other two are derived: the container publishes `20000:5432`, the
consumer is told 20000, and the rule set opens 20000.

An assignment is made once and kept, as a credential is. A port that
moved on every declaration would restart both ends each time and hand a
consumer a number that was true when it was read.

Ports the protocol fixes — mail on 25, submission on 587, DNS on 53 —
say so, and are then claims: one holder per machine, and the second is
refused by name at assignment. That is the mechanism the mesh already
has for what is singular on a machine, pointed at ports.

A mapping written the long way is left exactly as it is. Some things
must be pinned by hand, and quietly overruling somebody who wrote both
halves would be worse than not offering the short form.

Still open, and known: the substrate is not a module, so the mesh has
never heard of its own store and cannot yet assign around it. That is
what 028 will still be about after this.
2026-09-01 17:52:53 +02:00
..

Example modules

Manifests, not programs. They are here because the contract is easier to read as something that works than as a description of something that would.

Third-party software runs on the mesh, not of it (novox/hq ADR 0001). dnsmasq is not the mesh's, and neither is systemd-resolved — what is the mesh's is the fact only it can know, which is which machines exist and where they are. So the mesh writes that to a file and these read it.

Resolving a service named under a machine

postgres.novox.internal, plex.ace.internal. The first label is the service and the rest is the node, so anything under a node's name must resolve to that node and a proxy there routes by the name it was asked for. That routing is a separate concern and stays separate.

Two roles, and they are genuinely different things:

claims
serving the-dns-port answers the wildcards — dnsmasq.json
asking the-resolver-configuration decides what the machine asks — resolved-split-dns.json, resolv-conf.json

systemd-resolved cannot serve a wildcard, so it is only ever an asking module: it routes the mesh's suffix to something that can. Treating the two roles as one would produce a module that cannot work, which is the mistake worth naming.

Assign one of each. Two of either is refused by the mesh rather than fought over on the machine:

resolved-split-dns and resolv-conf both claim "the-resolver-configuration", and only one thing may hold it per node

ADR 0009's table names that resource /etc/resolv.conf, which is what it is, the way it writes the seat. A claim is a name in the catalogue's own form, so it is written as one.

Why neither needs to know the machine's address

Both would ordinarily need it — a resolver must bind somewhere, and a stub must be pointed somewhere — and a static manifest cannot know it.

Neither does, because both name things the mesh itself named: the private network's interface is mesh0 on every machine, and the address a resolver listens on for the machine's own use is 127.0.0.54 on every machine. A name the mesh chose is a name a manifest can use.

Asking for a bucket

object-store.json provides one, photos.json asks for one. Together they are the whole of an edge, and they are here as a pair because that is the only way to see the halves line up:

the provider says the consumer says
provides: s3-bucket requires: s3-bucket
receives — where to be told who asked contributes: {bucket: photos} — what it wants
grants — where their credentials land secrets — where to be given its key
serves — port, scheme, region binds — where to be told all that

The mesh adds the half neither can know: which machine the provider is on, and where it is on the private network. Neither manifest names an address, and that is what lets the same pair work on any mesh.

s3-bucket names the protocol, not the product (novox/hq ADR 0027). A consumer's code is written against the S3 API, and swapping one store for another does not break it — so the coupling is to S3. A database is the other case: an application is written against PostgreSQL or against SQL Server, so those provisions name the engine.

What the pair is checked for. That the names match, that each side says where it wants to be told, and that the consumer contributes the key the provisioner actually reads — bucket, not name. Contributing name (which is what a database consumer contributes) resolves perfectly and then fails on the machine with asked for a bucket and did not name it, which is a long way from the manifest that caused it.

The provisioner that makes the credential true lives in ../objectstore-provisioner, and is proven against a real store in the lab — including the assertion a database does not need, that a consumer cannot reach another consumer's bucket. One store holds every bucket behind one endpoint, so that isolation is a policy somebody wrote rather than a boundary the product has.