--- layer: to-be status: designed code: [] updated: 2026-09-01 decisions: - 02-DECISIONS/0009-modules-and-the-graph.md - 02-DECISIONS/0005-the-node-host.md - 02-DECISIONS/0027-a-provision-names-what-the-consumer-is-coupled-to.md --- # What a module must be able to say **Measured, not guessed.** 127 manifests in the system being replaced were read and every key counted, then set against what the new manifest can express. This document is the coverage checklist: what is already sayable, what is deliberately not, and what is missing. *Surveyed 2026-09-01. Counts are modules, not occurrences, unless stated.* ## Already sayable | what it says | used by | how it is said here | |---|---|---| | **depends on another module** | 65 | `requires` naming a module. A requirement naming a module means *that module*, not anything providing the name | | **system packages** | 28 | the `package` shape | | **a container** | 48 | the `container` shape, pinned by digest | | **systemd units** | 17 | a `file` for the unit, a `service` for the state it should be in | | **how to reach it** | 17 | `serves`, with the mesh adding which machine and where | | **a public name** | 11 | requiring `route` and contributing the name | | **ports it opens** | 11 | `listens`, from which filtering is computed | | **data directories, and who owns them** | 38 | `directory` with `owner`; never removed while holding anything ([ADR 0030](../../02-DECISIONS/0030-data-outlives-the-mesh-that-declared-it.md)) | | **what it provides and requires** | 15 + 2 | `provides` / `requires`, named for what the consumer is coupled to | | **restart when something changes** | 11 | `restart-on` | | **a generated credential** | 20 | `own-secrets`, sealed to the machine | | **a value that differs per node** | 43 | settings, and `computed` for what only the mesh knows | | **images built from source** | 2 | `build.artifacts` | ## Deliberately not sayable **Stage hooks — 36 modules.** Arbitrary code at install, configure and start. **The link may not carry an action** ([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)): what may be pushed is bounded by form, and a command to run is not a form. A module needing setup logic ships a program that reads what the mesh delivered and reconciles — which is what the provisioners are, and they are ~350 lines each including the reasoning. **Flavours — 6 modules.** Variants of one module. Retired in favour of claims ([ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md)): two display servers are two modules that both claim the seat, and adding a third changes nothing anywhere else. What is lost is `extends` chains, which were doing inheritance and are better as separate modules. ## Missing, and what each would take Ordered by how many modules need it. ### Tool servers — 56 modules **The largest single gap.** Over half the modules ship a `tools/` directory that becomes tools an agent can call on that node. Nothing in the new manifest says *this module offers tools*. A module can already run the server — it is a container or a service. What is missing is the convention that makes it reachable: something has to know the tools exist and route to them. That is plausibly not a manifest feature at all but a **provision** — a module provides `tools`, the session on that node requires them — which would need no new vocabulary. **Not yet decided.** ### Schema migrations — 14 modules A module with a database needs its schema brought up to date before it runs. The mesh does this for its own contexts and has no way for a *module* to declare it. The provisioner pattern covers it — a program that runs migrations and exits — but nothing expresses *this must happen before that starts*, which is the actual requirement. ### Configuration merging — 18 modules, 134 files Files assembled from a module's default plus per-node overrides, with a strategy (`replace`, `merge`) and a format (`toml`, `yaml`, `json`). Settings already merge into a file's content; what is missing is format-aware merging. **And it should stay missing.** A mechanism that understands TOML will be asked for YAML, then INI — which is how the arrangement being replaced became something nobody could hold in their head. The module knows its own format because it wrote the rest of the file. ### Health checks — 7 modules `{type: port|url, expect: …}`. The mesh knows whether a container is running, which is not the same as whether it answers — a distinction this project has paid for twice already. An `action` with a `verify` is exactly this shape, but actions may not arrive over the link, so a module cannot declare one. **This is the gap most worth closing**, because *running* and *answering* being conflated is a class of fault, not an inconvenience. ### Theme knobs — 3 modules, 101 values `{theme: {kind: color|font|string, label}}` — declared so a ricing tool can offer them. Settings already carry the value; what is missing is the **metadata** saying a value is presentable and what kind it is. Small, self-contained, and only interesting once something presents them. ### Event routing — 2 modules `{routing-key: tool}`, generating a consumer. Two modules; wait for a third before deciding. ### Publishing a package — 6 modules Modules published to a registry and consumed as libraries. This is a *build* output the mesh does not deliver to a node, so it may not belong here at all. ## What the survey found that is not about coverage **Declaration and reality had drifted in the system being replaced.** Several live provisions are brokered by modules whose manifests declare nothing — a speech-to-text engine served to a consumer on another node, an object-store bucket held by a module whose manifest mentions only its database. **A manifest that does not have to be true stops being true**, which is the argument for resolution refusing rather than warning. **Two derivations of the same fact.** A module's kind was computed in two places from different evidence — one from the manifest, one from what is on disk — producing different labels for the same module. There is one derivation here, and there should stay one. **A live listing returned credentials in plaintext.** Not a coverage question, but the reason sealing is worth its inconvenience.