Files
hq/03-DESIGN/00-as-is/03-provisioning.md
T
jschoubben c0b35652d0 The numbering is the flow: decisions are 02, design is 03
papa-hq reads 01 research -> 03 decision -> 02 design. The order is a
scar, not a choice: 02-DESIGN existed from its initial commit, and when
adr/ was finally promoted on 2026-07-13 it took the next free number
rather than its place in the sequence. By then design was too settled to
renumber.

hal-hq was three commits old, so it is not. adr/ becomes 02-DECISIONS and
02-DESIGN becomes 03-DESIGN, and following the folder numbers now walks
the process in the order it happens: research produces a decision, the
decision authorises a design.

00-GENESIS becomes 00-META, matching papa's rename from the same
restructure.

Every path reference rewritten across documents, frontmatter, playbooks
and skills. All links resolve; all 58 frontmatter blocks parse and their
path fields still point at files that exist.
2026-08-23 18:05:11 +02:00

89 lines
4.0 KiB
Markdown

---
layer: as-is
status: implemented
code: [hal]
updated: 2026-08-23
decisions:
- 02-DECISIONS/0005-capabilities-are-provisioned-on-declaration.md
- 02-DECISIONS/0004-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.