Jochen asked whether the order made sense. It did not -- it followed when things happened to be decided, which after consolidation is fictional anyway since record 5 alone folds decisions taken across a week. Concretely wrong before: the domain statement sat at 8, after five engineering rules; the constitution was scattered across 5, 12 and 17; the tiers landed at 15, 16, 21 and 22 with process records in between. Now it walks: what the mesh is (1-3), its tiers from the bottom up (4-8), what runs on them and how it gets there (9-10), how it is built (11-16), how it is checked (17-18), how we work (19-23). Two things made this safe rather than free. It is a permutation, not a compaction, so the renames go through temporary names -- otherwise two files want one slot and one is lost. And the reference rewrite is a single simultaneous pass, because almost every number moved into a slot another number was vacating; replacing one at a time would have cascaded and pointed things at the wrong record while still resolving. Verified: 284 [ADR NNNN](path) links across the repository, all with matching text and target. The ordering principle is now stated in 19 rather than left implicit -- the repository already said "the numbering is the flow" about its folders, and there was no reason for the records to be the exception.
132 lines
6.6 KiB
Markdown
132 lines
6.6 KiB
Markdown
# 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](../006-mesh-from-scratch/00-overview.md) 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 0005](../../02-DECISIONS/0005-the-node-host.md)
|
|
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.
|