Files
hq/01-RESEARCH/011-the-module-graph/features.md
T
jschoubben fa9889536c 011: tools have a different audience, migrations cross the edge, provisioning is early
Three additions, and the third kills an assumption.

Tools are the most common content in the catalogue — 56 of 126 modules, more
than carry a service — and they survive the split without fitting either half. A
tool is not an artifact and not node state; it is a contract the mesh publishes
on a module's behalf, and what consumes it is an AGENT rather than another
module. That is a second audience the design has not described. Whether it is
one relation with two audiences or two relations is cheap to decide now and
expensive later.

A migration belongs to the CONSUMER and runs on the PROVIDER. A game's
migrations run against the database the store granted it: owned by the consumer,
hosted inside something it does not control, ordered after the provisioning edge
because there is nothing to migrate until the grant exists, and scoped to that
grant. Ownership crosses the edge, which nothing in provides and requires
expresses — and it gives a consumer's own install an internal order, provisioned
then migrated then started, that depends on an edge rather than on its contents.

And provisioning is EARLY, not late. The assumption worth killing is that it is
something the control plane does for consumers once a mesh is running. The
mesh's own registry database is provisioned before there is a mesh, and so is
its virtual host on the broker: the store runs from the carried bundle, a
database is created in it, the mesh's own schema is applied, and only then does
a control plane exist. Steps two and three happen before there is a mesh to do
them, so provisioning is part of the bootstrap and part of what the bundle has
to express.

Which strains ADR 0043. The host applies declared state ON THIS MACHINE, and a
database inside a running store is not a file or a unit. At bootstrap it is at
least local — the store is on the same machine. Afterwards a consumer on one
node provisioned from a store on another is the ordinary case and reaching it is
not the host's job. The same operation is local at bootstrap and remote later,
which is either two mechanisms or one with a tier boundary crossing inside it.
Currently the sharpest unresolved thing in the effort.
2026-08-26 23:02:52 +02:00

6.6 KiB

What a feature is, and what it splits into

Measured against origin/main of the code repository, 2026-08-26.

The operator wants the feature concept gone. Research 006 left it open — "does feature survive? The skeleton splits it in two and argues the conflation is what makes the delivery pipeline hard to reason about."

This is what it actually is, and what it turns into.

What it is today

A feature is a kind of content a module can carry, detected from what its directory contains rather than declared. Twenty-one of them, each with a handler that owns its whole lifecycle:

configs  dist-assets  events  hooks  migrations  migration-artifact  npm  npm-install
prerequisite-env  prerequisite-packages  prerequisite-provision  provision-migrations
provision-seeds  seeds  service  systemd  tools  verifiers  verify  vhost  detect

Each moves through six stages: build, publish, install, configure, start, verify.

Finding — every handler implements every stage

The structural evidence, and it is not a style problem.

handler stages it implements
configs build · configure · install · start · verify
service build · configure · install · start · verify
systemd build · configure · install · start · verify
tools build · configure · install · start · verify
vhost build · configure · install · start · verify
migrations build · configure · install · start · verify
npm build · publish · install · configure · start

configs writes files onto a node. It has nothing to build, and it has a build stage. npm publishes a package to a registry. It has nothing to start, and it has a start stage.

One interface spans build-time and apply-time, so every kind of content must implement both halves and most of them do nothing in one. That is the conflation, and the emptiness is what makes the pipeline hard to reason about: a stage that does nothing and a stage that failed to do anything look identical from outside.

What it splits into

The twenty-one are not one kind of thing. They are four, and they belong to different tiers.

Artifacts — built once per version, then published. npm, dist-assets, migration-artifact. Nothing about a node is involved; the output is a thing that exists in a registry. Tier 2, delivery.

Resources — desired state on a machine. configs, service, systemd, vhost, tools. Applied, converged, idempotent — which is exactly what ADR 0043 already describes and what the host already does. Tier 0.

Actions — run once, against something that is not this machine. migrations, seeds, provision-migrations, provision-seeds, hooks. A migration runs against a database, and the database may be on another node entirely. Neither an artifact nor node state, which is why they sit awkwardly in a scheme built for both. Tier 2, and the operator wants seeds gone.

Checks — assertions, not changes. prerequisite-env, prerequisite-packages, prerequisite-provision, verify, verifiers, detect. The prerequisites are requirements in disguise — a module saying what must be true before it can be installed, which is precisely what an edge in the graph says. The verifiers are the read-back the host already performs.

So the answer is: it splits, and the split is a tier boundary

feature is one word for four things spanning three tiers. That is why every handler implements every stage, why half of them are empty, and why the pipeline is hard to reason about.

Nothing replaces it, because it was never one concept:

Was a feature Becomes Whose
npm, dist-assets, migration-artifact an artifact delivery
configs, service, systemd, vhost, tools a resource in a declaration the host
migrations, hooks an action against something else delivery
seeds nothing — the operator wants them gone —
prerequisite-* an edge in the graph the catalogue
verify, verifiers the host's read-back the host

Tools are a facet, and their consumer is not a module

The most common content in the catalogue: 56 of 126 modules carry a tool surface, more than carry a service.

It survives the split, and it does not fit either half cleanly. A tool is not an artifact and not node state — it is a contract the mesh publishes on a module's behalf, and what consumes it is an agent, not another module.

That is a second audience, and the design has only described one. A module provides things other modules require; a module also provides things agents call. Same word, different consumer, different lifecycle — a tool appears when the module is assigned somewhere and disappears when it is not, and nothing in the graph edges says so.

Whether that is one relation with two audiences or two relations is undecided, and it is the kind of question that is cheap now and expensive later.

What is lost, and should not be

Detection. Features are detected from the directory rather than declared, and the reason is good: a declared list and the directory it describes drift, and the directory is the one that is true. That property is worth keeping whatever the concept is called — a module that says it has migrations and has none, or has them and does not say so, is a fault nobody sees until it matters.

Under the split, detection still applies: what a module contains is read from what is there. What it requires, provides and excludes is declared, because none of that is visible in a directory.

That line is worth stating precisely, because it is the one the current design got right: content is detected, relationships are declared.

Open

  • Is an action a resource? A migration is not node state and not an artifact. It could be a resource type the host applies with the target being a database rather than a machine — which would collapse the third category into the second, at the cost of the host reaching something that is not the machine it is on. That cost looks too high, but it has not been argued.
  • Where do hooks go? They are arbitrary code a module runs at a stage — the escape hatch. A design with no escape hatch is either very good or has not met reality yet, and this one has not.
  • What does the pipeline schedule, once features are gone? It currently schedules features. If the four categories have different lifecycles, the unit of work is different for each, and what the coordinator orchestrates needs naming.