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.
89 lines
4.0 KiB
Markdown
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.
|