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.
This commit is contained in:
2026-09-01 17:52:53 +02:00
parent a5d85266d0
commit 1f5b70a995
17 changed files with 617 additions and 47 deletions
+45 -2
View File
@@ -196,7 +196,11 @@ func theRestOfTheMesh(ctx context.Context, inv *inventory.Inventory,
// What that module says a consumer needs to know, with that node's settings on
// it: a port somebody moved on the provider is a port its consumers must be told
// about, and the two coming from different places is how they come to disagree.
serves := m.Serves[name]
assigned, err := portsOn(ctx, inv, o.node.Name, m.Module)
if err != nil {
return catalogue.World{}, err
}
serves := catalogue.ServedOn(m, name, assigned)
if len(serves) > 0 {
layers, err := inv.SettingsFor(ctx, o.node.Name, m.Module)
if err != nil {
@@ -253,6 +257,28 @@ func declarationWith(ctx context.Context, open *stores, node string,
if err != nil {
return nil, err
}
// Where this machine puts what each module needs reachable (novox/hq ADR 0038).
//
// **Assigned here rather than written by a module**, because a module is written once and
// assigned anywhere: any number it picks is a guess about a machine it has never seen. Made
// before the declaration is composed, because the container's mapping, the rule set and what a
// consumer is told are all derived from it.
ports := map[string]map[int]int{}
for _, m := range plan.Modules {
for _, l := range m.Listens {
at, err := inv.PortFor(ctx, node, m.Module, l.Port, l.Fixed)
if err != nil {
return nil, fmt.Errorf(
"%s needs %d reachable on %s and it could not be assigned: %w",
m.Module, l.Port, node, err)
}
if ports[m.Module] == nil {
ports[m.Module] = map[int]int{}
}
ports[m.Module][l.Port] = at.Machine
}
}
// And each module's own secrets — a superuser password, an administrator, an account. Made
// per node, so a module running on three machines has three.
needed := map[string]map[string]string{}
@@ -302,7 +328,7 @@ func declarationWith(ctx context.Context, open *stores, node string,
}
return plan.Declaration(catalogue.Rendering{
Settings: settings, Generators: gens, Grants: grants, Needed: needed,
Settings: settings, Generators: gens, Grants: grants, Needed: needed, Ports: ports,
Certificate: certificate, Authority: authority, Mesh: private, Names: names})
}
@@ -562,3 +588,20 @@ func keyFor(ctx context.Context, open *stores, licence, node, module string) (st
}
return held.KeyFor(ctx, licence, node, module)
}
// portsOn is one module's assignments on one machine, by the port the software uses.
func portsOn(
ctx context.Context, inv *inventory.Inventory, node, module string,
) (map[int]int, error) {
all, err := inv.PortsFor(ctx, node)
if err != nil {
return nil, err
}
out := map[int]int{}
for _, a := range all {
if a.Module == module {
out[a.Wanted] = a.Machine
}
}
return out, nil
}