Jochen asked whether the order made sense. It did not -- it followed when things happened to be decided, which after consolidation is fictional anyway since record 5 alone folds decisions taken across a week. Concretely wrong before: the domain statement sat at 8, after five engineering rules; the constitution was scattered across 5, 12 and 17; the tiers landed at 15, 16, 21 and 22 with process records in between. Now it walks: what the mesh is (1-3), its tiers from the bottom up (4-8), what runs on them and how it gets there (9-10), how it is built (11-16), how it is checked (17-18), how we work (19-23). Two things made this safe rather than free. It is a permutation, not a compaction, so the renames go through temporary names -- otherwise two files want one slot and one is lost. And the reference rewrite is a single simultaneous pass, because almost every number moved into a slot another number was vacating; replacing one at a time would have cascaded and pointed things at the wrong record while still resolving. Verified: 284 [ADR NNNN](path) links across the repository, all with matching text and target. The ordering principle is now stated in 19 rather than left implicit -- the repository already said "the numbering is the flow" about its folders, and there was no reason for the records to be the exception.
89 lines
3.9 KiB
Markdown
89 lines
3.9 KiB
Markdown
---
|
|
layer: as-is
|
|
status: implemented
|
|
code: [hal]
|
|
updated: 2026-08-23
|
|
decisions:
|
|
- 02-DECISIONS/0009-modules-and-the-graph.md
|
|
- 02-DECISIONS/0011-managed-files-are-generated-never-edited.md
|
|
---
|
|
|
|
# Provisioning
|
|
|
|
A module states what it needs. The mesh makes it exist, generates the credential, records the
|
|
grant, and puts the values where the module will read them. Nobody writes a credential and
|
|
nobody writes a topology.
|
|
|
|
This is the mesh's core concern rather than its plumbing — the property everything else is
|
|
built on.
|
|
|
|
## The declaration
|
|
|
|
A **provider** declares the resource type it can create and the network on which that resource
|
|
is reachable from a container.
|
|
|
|
A **consumer** declares, per requirement: the provider module, the resource type, optionally a
|
|
name and a target node, and a mapping from the resource's connection fields onto its own
|
|
environment variables.
|
|
|
|
The consumer must also declare those variables as empty in its environment section. A mapped
|
|
field with no declared variable is dropped — the value is produced and then discarded, because
|
|
the generator only emits variables the manifest knows about.
|
|
|
|
## What happens
|
|
|
|
Inside the pipeline the sequence is orchestrated by the coordinator and is not optional:
|
|
|
|
1. The provisioner creates the resource and its credential, records the grant, and writes the
|
|
mapped values as database overrides.
|
|
2. The synchroniser regenerates the module's environment from those overrides.
|
|
3. Only then is the service started.
|
|
|
|
Each step waits for the previous one's event. A module that declares no requirements skips the
|
|
first step entirely — which is correct, and means the absence of provisioning is
|
|
indistinguishable from provisioning that did not run.
|
|
|
|
Outside the pipeline the same two steps exist as direct operations, for debugging. They are
|
|
not the normal path.
|
|
|
|
## What can be provisioned
|
|
|
|
Providers exist for relational databases of two kinds, an object store, a cache, message-broker
|
|
virtual hosts and access, an identity provider's clients, and a download category. Each returns
|
|
the connection fields a consumer maps from — host, port, user, password, and whatever else the
|
|
resource type implies.
|
|
|
|
A provider with no registered provisioner still records a grant, with an empty connection. This
|
|
is deliberate and easy to misread: the grant exists, so the requirement looks satisfied, and
|
|
nothing was created.
|
|
|
|
## Cross-node grants
|
|
|
|
A requirement may name the node whose provider should satisfy it. The grant records consumer
|
|
node and provider node separately, so a module on one node holding a database on another is
|
|
the ordinary case rather than a special one.
|
|
|
|
For a containerised consumer, the provisioner returns the provider's routable name rather than
|
|
a loopback address — the value has to be correct from **inside** a container on another
|
|
machine.
|
|
|
|
## Where this hurts
|
|
|
|
**Rotation has no fan-out.** A resource whose credential is shared by several consumers can be
|
|
rotated by provisioning a new one, and the peers holding the old credential are not told. This
|
|
has locked the mesh out of its own broker, and has caused a node's adoption to rotate a live
|
|
shared password without informing anything that held it. Granting is easy; regranting is not
|
|
modelled.
|
|
|
|
**A frozen password outlives its generation.** A generated password is written once. If the
|
|
resource's persistent data directory already exists from an earlier initialisation, the stored
|
|
credential and the generated one diverge, and the symptom is an authentication failure that
|
|
looks like a configuration error.
|
|
|
|
**Migrations against a provisioned resource run on the consumer's node**, not the build node,
|
|
and read the deployed artifact rather than the source tree. Both facts were wrong in the
|
|
implementation for a period during which new provisioning migrations silently never ran.
|
|
|
|
**A grant is not a check.** The record says a resource was provisioned. Nothing verifies it
|
|
still exists, still has the recorded credential, or is reachable from where the consumer runs.
|