The design said a module's manifest sits at a repository's root, full stop, which means one repository per module. Nothing that exists is shaped that way: the catalogue holds sixty-seven modules one to a directory, no code repository has a manifest at its root, and the system being replaced has always built a module from a repository and a path. So the builder could be asked to build nothing that exists — pointed at the catalogue it finds no manifest, pointed at a module's source it finds none either. Recorded as a decision because it moves the core modules' manifests beside their source, and corrects the design that said otherwise. Also corrects, in the same document, how the three things the build loop cannot produce actually arrive. They were written as though all three were carried in. Only the control plane is: the registry is pulled from the public internet, and the builder has no route at all — which is now stated as the open one rather than implied to be solved. Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
4.7 KiB
topic, status, date, deciders, reconstructed, extends
| topic | status | date | deciders | reconstructed | extends |
|---|---|---|---|---|---|
| building it | accepted | 2026-09-12 | jochen | false | 0009-modules-and-the-graph.md |
69. A module is a repository and a path within it
Context
The builder clones one repository and reads module.json at its root. The to-be design says
so in as many words — "one file at the root" — and the code implements it: clone, read the root
manifest, build what it declares.
Nothing that exists is shaped that way. The catalogue holds sixty-seven modules, each in its own directory, and has no manifest at its root. None of the five code repositories has one either. So today the builder cannot be asked to build any module that exists: pointed at the catalogue it finds no manifest, and pointed at a module's source it finds no manifest.
The system being replaced already works the other way, and has for years: a monorepo with one directory per piece of software, and the coordinator builds a module from a repository and a path inside it. The root-only assumption is not a simplification of that — it is a different model that was never reconciled with it.
And it splits what a build needs into two places. The control plane's manifest sits in the catalogue; the source it describes sits in the control plane's own repository. A build must read one tree, so under the root-only model neither location can be built from.
Considered Options
1. One repository per module. Rejected. Sixty-seven repositories for sixty-seven modules, most of which are a single manifest naming a public image, and every one needing its own creation, permissions and lifecycle. It also contradicts ADR 0015, which put applications in their own repositories precisely because modules do not need one.
2. Keep manifests in the catalogue and source elsewhere, and have a build fetch both. Rejected. A build would clone two trees whose versions can disagree, so "what commit is this module?" stops having one answer — and that question is the whole basis of knowing when to rebuild.
3. A module is a repository and a path within it. Chosen. It is what the current system does, what the catalogue already looks like, and it keeps a module's description beside the thing it describes.
Decision
A module is named by a repository and a path within it. The path holds module.json, and
everything that manifest declares is produced from that path. A module whose path is the root is
the ordinary case of this, not a separate one.
A module's manifest lives beside its source. Where a module has code, its directory holds both, so one commit answers "what is this module, and what is it made of". Where a module has no source — a manifest naming a public image — the directory holds only the manifest, and there is nothing to build.
This moves the core modules. The control plane and the builder are built from the control plane's repository, so their manifests belong in that repository at their own paths, not in the catalogue. The catalogue keeps the modules whose source it holds, and the modules that are only a manifest.
One commit, one module version. Because a module is one path in one repository, the commit that built it identifies it exactly, and "the source has moved ahead of what the mesh holds" stays a question with a yes or no answer.
Consequences
The builder gains a path alongside the repository and the ref. A build is repository, path, ref,
and the manifest it returns is the module the mesh records.
The catalogue stops being the place every manifest lives, and becomes the place manifests live when their module has no other home. That is a smaller claim than it sounds: most of the sixty-seven stay exactly where they are.
Two repositories change shape — the control plane's gains manifests for the modules built from it. Nothing else moves.
A repository can hold modules that are built and modules that are not, and no rule distinguishes them beyond whether their manifest declares anything to build.
How this is checked
| Rule | Checked by |
|---|---|
| A module is buildable from its repository and path | The builder is asked for a module by repository and path, and returns a manifest whose artifacts are pinned to digests the mesh's registry assigned. |
| A manifest sits beside what it describes | A module declaring something to build, whose path holds no source to build it from, is refused at build time rather than producing an empty result. |
| One commit identifies one module | Two builds of the same repository, path and commit produce the same digests. |
| The core modules are built like any other | The control plane is rebuilt from its own repository and path, and the running mesh is upgraded to it — the same path an ordinary module takes. |