Files
hq/03-DESIGN/00-as-is/02-modules-and-manifests.md
T
jschoubben c0b35652d0 The numbering is the flow: decisions are 02, design is 03
papa-hq reads 01 research -> 03 decision -> 02 design. The order is a
scar, not a choice: 02-DESIGN existed from its initial commit, and when
adr/ was finally promoted on 2026-07-13 it took the next free number
rather than its place in the sequence. By then design was too settled to
renumber.

hal-hq was three commits old, so it is not. adr/ becomes 02-DECISIONS and
02-DESIGN becomes 03-DESIGN, and following the folder numbers now walks
the process in the order it happens: research produces a decision, the
decision authorises a design.

00-GENESIS becomes 00-META, matching papa's rename from the same
restructure.

Every path reference rewritten across documents, frontmatter, playbooks
and skills. All links resolve; all 58 frontmatter blocks parse and their
path fields still point at files that exist.
2026-08-23 18:05:11 +02:00

114 lines
6.0 KiB
Markdown

---
layer: as-is
status: implemented
code: [hal]
updated: 2026-08-23
decisions:
- 02-DECISIONS/0002-everything-is-a-module.md
- 02-DECISIONS/0006-schema-changes-are-numbered-migrations.md
- 02-DECISIONS/0007-no-npm-workspace.md
---
# Modules, manifests and features
Everything the mesh installs is a module: a directory with a manifest. There is no second
mechanism.
## What a manifest declares
| Declares | Meaning |
|---|---|
| Identity | Name and version. **Version is owned by the builder** — a hand-edited version is a defect, and reviewers revert it. |
| Environment | Every variable the module reads, with how each is produced: a static default, a generated secret, a value pulled from the node's own record, or a template composed from the others. A variable not declared here is invisible to the mesh and will not be generated, injected or audited. |
| What it provides | The resource type this module can provision for others, and the network on which it is reachable. |
| What it requires | Resources it needs from other modules, and the mapping from each resource's connection fields onto its own environment variables. |
| Service shape | The primary container, and the data directories that must exist with the right ownership before it starts. |
| Exposure | The public names this module's interfaces answer on, declared portably so the reverse proxy configuration can be generated rather than written. |
| Images | Container images this module builds, so the pipeline builds and publishes them before publishing the module. |
## Features are the unit of work
A module is not the unit the pipeline addresses. A **feature** is.
A feature is a kind of content a module can carry: a service, a set of capabilities, a
long-running process, managed configuration files, migrations, firewall rules, an installable
application. One module can carry several.
Features are **detected from directory contents**, not declared. A module with a capabilities
directory has that feature; a module with a daemon directory has that one. An explicit
declaration was supported and is now discouraged, because a declared list and the directory it
describes drift, and the directory is the one that is true.
Every pipeline command and event names a feature. There is no per-module build.
### What detection costs
Detection makes the manifest shorter and the truth singular, and it makes the directory
structure load-bearing in a way that is not obvious from reading a manifest. Renaming a
directory changes what a module *is*, silently. The recurring failure is a hook named for a
feature the module does not carry: it is skipped without complaint, and the change it was
supposed to make simply never happens.
An unknown key in a manifest is likewise accepted in silence — which is how a firewall rule
can appear to restrict a port and restrict nothing (see
[`04-ISSUES/003`](../../04-ISSUES/003-firewall-scope-is-read-by-no-code/00-report.md)).
## Kinds of module
The kinds are not a type system — they are what the detected features add up to.
- **A service module** carries a container definition. It gets a runtime directory, generated
environment, data directories, and is started under supervision.
- **A capability module** carries capabilities and no service. It contributes what a node can
do, locally and to its peers.
- **A flag module** carries nothing but a manifest. Its presence in a node's assignment is the
entire content: it gates behaviour elsewhere.
- **Combinations** are ordinary. A database module is a service *and* a capability provider
*and* a provisioner.
## Selections
A module can ship variants of the same feature and a node takes the one that fits it — a build
for one accelerator or another, a configuration for a public node or a private one. The
artifact stays selection-blind; the choice is a property of the assignment, held in the mesh
database.
This is one of the areas where behaviour has repeatedly diverged from intent, in both
directions: selection files that were never packaged into the artifact at all, and a stale
staged override on a node that silently won over the newly selected one. Both classes are
recorded in the knowledge base; both presented as "the change did not apply" with no error.
## Dependencies between modules
Modules depend on each other, above all on the shared library they all build against. There is
**no workspace** ([ADR 0007](../../02-DECISIONS/0007-no-npm-workspace.md)): each module is a standalone
package consuming published dependencies, including the mesh's own.
The pipeline resolves modules into dependency **levels** and completes a level before starting
the next, so a module always builds against its dependencies as just published.
The cost is a publish-and-consume round trip for every cross-package change, and the absence of
any repository-wide build. One thing that assumed a repository-wide build has stayed broken
since (see
[`04-ISSUES/005`](../../04-ISSUES/005-pipeline-test-harness-unbuildable/00-report.md)).
## Persistent state
A module that owns state owns its migrations: numbered, written in the module's own language,
compiled with it, frozen once they have run anywhere, and idempotent so that re-running is safe
([ADR 0006](../../02-DECISIONS/0006-schema-changes-are-numbered-migrations.md)).
Two kinds exist and the distinction matters: migrations against the module's **own** local
state, and migrations against a **provisioned** resource, which run on the node that consumes
the resource rather than on the node that built the module.
## A documented rule with no enforcement
Every module exposing capabilities is documented as required to declare the mesh's core runtime
as a dependency. **Zero of the catalogue's modules do.**
This is recorded here rather than quietly corrected, because it is the clearest instance of the
rule this repository states about itself: a rule whose enforcement does not exist is
indistinguishable from a wrong one, and costs more, because people believe it. Whether the rule
or the catalogue is wrong has not been decided.