Files
hq/04-ISSUES/023-a-consumer-cannot-build-a-connection-string/00-report.md
T
jschoubben 80f18caf03 022 fixed; 023 filed — a password is not a connection
022 turned out to have a silent half worth recording: the provider
refuses loudly and names the modules, which reads as a decision, while
the consuming node does not refuse at all. Three modules wanting one
database produce one need, so two of them get no credential file and
each starts and fails to authenticate with nothing saying why.

023 is what remained after fixing it. A consumer now gets its own
password, in whatever shape its configuration wants, and still cannot
connect: the user name is invented by the provisioner and recorded
nowhere in the mesh, and the host and port sit in a JSON binding that an
application reading KEY=value cannot use.

The asymmetry is backwards and the coverage document now says so. The
secret is the hard case, because the mesh must not be able to read it,
and the secret is the part that arrives. The host and port are ordinary
facts the mesh holds in the clear, and they are the ones stuck.

Keycloak, Gitea, Mailu and MinIO all parse and resolve and none of them
can start. This is what stands between the module set and a running one.
2026-09-01 02:40:40 +02:00

3.8 KiB

status, opened, located-in, fixed-by, amended-design
status opened located-in fixed-by amended-design
located 2026-09-01
mesh-control

023 — A consumer is given every part of a connection except the two it cannot invent

Symptom

A module that requires a database is now given its password in whatever shape its configuration needs (022 and the sealed-placeholder work). It still cannot connect, because a password is not a connection.

What it is given is a binding, as JSON:

provision  postgres-database
from       the node providing it
at         that node's address on the private network
serves     what the provider said a consumer must know — the port

What it needs, to write KC_DB_URL or GITEA__database__USER, is the host, the port, the database name and the user name. Two of those are missing, for two different reasons.

The user name is nobody's to say

The provisioner invents it — mesh_<node>_<module> — and nothing else in the mesh knows that string. The control plane does not record it, the binding does not carry it, and the consumer cannot derive it without hard-coding another module's naming convention.

So the one identifier a consumer must present in order to authenticate is the one thing no part of the mesh will tell it. It works today only because nothing has yet had to write a connection string; every proof so far stopped at "the credential arrived".

The values cannot reach the file that needs them

The binding is a JSON document. The consumers are containers reading KEY=value, or a program reading a YAML file, or one reading an attribute inside a different JSON document. A sealed secret can now be placed inside any of those — the module writes the file with a hole in it and the host fills the hole on the machine. The bound values have no such route, so the half of the connection that is not secret is the half that cannot be delivered.

This asymmetry is backwards. The secret is the hard case, because the mesh must not be able to read it. The host and port are ordinary facts the mesh knows in the clear, and they are the ones stuck in a document nothing can read.

Why it was not noticed

Every provider so far has been reached by a provisioner, a program written for the job, which reads the JSON because it was built to. The first consumers to need a plain configuration file were the first real applications. The binding was designed for the program and then handed to the application.

There is also a stale comment saying a binding "carries no credential: the mesh has no way to issue one yet". That stopped being true when 021 was fixed.

What a fix has to settle

  • Who names the role. Either the mesh records what the provisioner will create, or the consumer contributes the name it wants and the provisioner uses it. The second is more in keeping with the rest — a consumer already contributes the database name it wants — and it removes an invented convention rather than documenting one.
  • How a bound value reaches a file. The symmetric answer to the sealed placeholder, and simpler: these values are not secret, so the control plane can put them in before sending and the host learns nothing new.
  • That it stays name-agnostic. The control plane must not learn what a postgres-database is. What the keys mean is agreed by the requirement's name (ADR 0027), so whatever is added has to work for a bucket and a mail relay without being told about either.

Blocked on this

Keycloak, Gitea, Mailu and MinIO all have manifests that parse and resolve, and none of them can start. This is what stands between the module set and a running one.