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.
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
---
|
||||
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.
|
||||
Reference in New Issue
Block a user