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.