From c9c2dfe6868d8a744dc4c9fe1088be705d60bae8 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 26 Aug 2026 22:51:19 +0200 Subject: [PATCH] 011: what a feature is, and what it splits into MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The operator wants features gone, and 006 left it open. Measured, and the answer is that nothing replaces them because they were never one concept. A feature is a kind of content a module carries, detected from its directory: twenty-one of them, each with a handler owning six stages — build, publish, install, configure, start, verify. The structural finding: EVERY handler implements EVERY stage. `configs` writes files onto a node, has nothing to build, and has a build stage. `npm` publishes to a registry, has nothing to start, and has a start stage. One interface spans build-time and apply-time, so every kind of content must implement both halves and most do nothing in one — and a stage that does nothing looks exactly like a stage that failed to do anything. They split four ways, across three tiers. Artifacts built once per version and published, where no node is involved — delivery. Resources that are desired state on a machine, which is what ADR 0043 already describes and the host already does — tier 0. Actions run once against something that is not this machine, like a migration against a database on another node — delivery, and seeds go entirely. And checks: the prerequisites are REQUIREMENTS IN DISGUISE, a module saying what must be true before it can be installed, which is what an edge in the graph says; the verifiers are the read-back the host already performs. So `feature` is one word for four things spanning three tiers, which is why the pipeline is hard to reason about. One property must survive the split, and it is the thing the current design got right: content is DETECTED, relationships are DECLARED. A module that says it has migrations and has none is a fault nobody sees until it matters — but what it requires and provides is not visible in a directory and has to be said. --- .../011-the-module-graph/00-overview.md | 9 ++ 01-RESEARCH/011-the-module-graph/features.md | 114 ++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 01-RESEARCH/011-the-module-graph/features.md diff --git a/01-RESEARCH/011-the-module-graph/00-overview.md b/01-RESEARCH/011-the-module-graph/00-overview.md index ae77ea9..aedff08 100644 --- a/01-RESEARCH/011-the-module-graph/00-overview.md +++ b/01-RESEARCH/011-the-module-graph/00-overview.md @@ -17,6 +17,15 @@ touches: Whether the catalogue's missing structure is a **graph** — modules declaring what they need, what they offer, and what they exclude — and what that replaces. +**[`features.md`](features.md) answers what happens to `feature`.** It is one word for four +things spanning three tiers — artifacts built once per version, resources applied to a machine, +actions run against something that is not this machine, and checks that are requirements in +disguise. Measured: every one of the twenty-one handlers implements all six stages, so `configs` +has a build stage with nothing to build and `npm` has a start stage with nothing to start. That +emptiness is the conflation, and it is why a stage that did nothing and a stage that failed look +alike. Nothing replaces it, because it was never one concept. **One property is worth keeping: +content is detected, relationships are declared.** + **[`cases.md`](cases.md) enumerates what a module can be** — twenty kinds of thing the mesh has to install, run, own or know about — and extracts the axes a manifest must express. Two of those axes appear in no current thinking: **how many instances** a thing may have, and **whether two diff --git a/01-RESEARCH/011-the-module-graph/features.md b/01-RESEARCH/011-the-module-graph/features.md new file mode 100644 index 0000000..82d05d4 --- /dev/null +++ b/01-RESEARCH/011-the-module-graph/features.md @@ -0,0 +1,114 @@ +# 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 0043](../../02-DECISIONS/0043-a-declaration-is-an-ordered-list-of-owned-resources.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 | + +## 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.