Files
hq/02-DESIGN/00-as-is/02-modules-and-manifests.md
T
jschoubben 702efca6bb Base layer: the mesh as it is, under the mesh as it should be
HQ held only the to-be. Every reader had to already know the system the
decisions were about, and an as-is claim had nowhere to live except inside
an intention.

Adds 02-DESIGN/00-as-is — eleven documents written from the implementation
and the operational record, not from intent, including the parts nobody
would choose again. The two existing designs move under 01-to-be. Layers
are declared in frontmatter and never mix: a design that ships does not
move, its as-is counterpart is written, and both stand.

Back-fills adr/0001-0014 for decisions taken in implementation and never
recorded — the broker, the module abstraction, the mesh database, managed
files, provisioning, migrations, the workspace removal, failing loudly,
the constitution, application placement, linking, the employee model, the
artifact, the three silos. Each marked reconstructed, dated from the
history, and citing the evidence it was recovered from. The two existing
records renumber to 0015 and 0016 so the ledger runs oldest first;
0017 extends 0015 to modules outside the core, principle only — the
domain list is deliberately not invented here.

how-we-build.md becomes the source of the mesh constitution, with a sync
playbook, so the enforced copy stops being the only one that is true.

Process becomes explicit: five playbooks, eight thin skills that defer to
them, a repository map, and AGENTS.md with CLAUDE.md as its include.

The five Observations become 04-ISSUES 001-005 where they can be owned and
closed. 006 is new and uncomfortable: HQ is not indexed into the knowledge
base. That claim is what decision 27 rests on, it was never checked, and
the README now says so instead of repeating it.

Also corrects the ADR index into something generated, the "02-DESIGN is
empty" claim, the VISION.md pointer that did not survive the repo split,
and a note asserting the symlink rule was contradicted — it was a
misreading; the rule forbids hand-made links, the installer links by design.
2026-08-23 03:08:26 +02:00

114 lines
5.9 KiB
Markdown

---
layer: as-is
status: implemented
code: [hal]
updated: 2026-08-23
decisions:
- adr/0002-everything-is-a-module.md
- adr/0006-schema-changes-are-numbered-migrations.md
- adr/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](../../adr/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](../../adr/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.