Files
hq/02-DESIGN/00-as-is/03-provisioning.md
T
jschoubben 702efca6bb Base layer: the mesh as it is, under the mesh as it should be
HQ held only the to-be. Every reader had to already know the system the
decisions were about, and an as-is claim had nowhere to live except inside
an intention.

Adds 02-DESIGN/00-as-is — eleven documents written from the implementation
and the operational record, not from intent, including the parts nobody
would choose again. The two existing designs move under 01-to-be. Layers
are declared in frontmatter and never mix: a design that ships does not
move, its as-is counterpart is written, and both stand.

Back-fills adr/0001-0014 for decisions taken in implementation and never
recorded — the broker, the module abstraction, the mesh database, managed
files, provisioning, migrations, the workspace removal, failing loudly,
the constitution, application placement, linking, the employee model, the
artifact, the three silos. Each marked reconstructed, dated from the
history, and citing the evidence it was recovered from. The two existing
records renumber to 0015 and 0016 so the ledger runs oldest first;
0017 extends 0015 to modules outside the core, principle only — the
domain list is deliberately not invented here.

how-we-build.md becomes the source of the mesh constitution, with a sync
playbook, so the enforced copy stops being the only one that is true.

Process becomes explicit: five playbooks, eight thin skills that defer to
them, a repository map, and AGENTS.md with CLAUDE.md as its include.

The five Observations become 04-ISSUES 001-005 where they can be owned and
closed. 006 is new and uncomfortable: HQ is not indexed into the knowledge
base. That claim is what decision 27 rests on, it was never checked, and
the README now says so instead of repeating it.

Also corrects the ADR index into something generated, the "02-DESIGN is
empty" claim, the VISION.md pointer that did not survive the repo split,
and a note asserting the symlink rule was contradicted — it was a
misreading; the rule forbids hand-made links, the installer links by design.
2026-08-23 03:08:26 +02:00

3.9 KiB

layer, status, code, updated, decisions
layer status code updated decisions
as-is implemented
hal
2026-08-23
adr/0005-capabilities-are-provisioned-on-declaration.md
adr/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.