Designed with no reference to what came before, which was asked for. The system this replaces has features — several deployable units inside one module — and they are deliberately absent. That closes something ADR 0001 has been carrying as an open prerequisite. It lists "named features with per-node opt-in" as required, or "every independently deployable unit becomes a module again and the count returns". The premise was right and the remedy already exists in another form: several modules, assignment per node, and a module with requirements and no files of its own. `networking` is exactly that. The count does not return because what made it return — a module is expensive, so put several things in one — is gone. A module here is a manifest and usually nothing else. The manifest in a repository names artifacts; the manifest the mesh holds names digests. Two documents, because a digest is not knowable until something is built and a repository carrying one is wrong the moment anybody edits anything. The builder runs on a node. Building needs a container runtime and a working tree, and what the control plane may send a machine is bounded by the declaration language. A control plane holding a container socket would be the one component that can do anything anywhere. And the host's vocabulary grew from six shapes to eight — user and archive — with the reasoning for each and for the refusals that came with them. The count is asserted by a test precisely because every addition widens what a compromised control plane can express.
4.9 KiB
layer, status, code, updated, decisions
| layer | status | code | updated | decisions | |||||
|---|---|---|---|---|---|---|---|---|---|
| to-be | designed |
|
2026-08-30 |
|
A module repository, and what builds it
Designed from what the mesh needs, not from what came before. The system this replaces has a concept of features — several independently-deployable units inside one module — and it is deliberately absent here.
Features are unnecessary, and that closes an open prerequisite
ADR 0001 lists named features with per-node opt-in as a prerequisite, on the grounds that without it "every independently deployable unit inside a context becomes a module again and the count returns."
The premise was right and the remedy already exists in another form. What features were for is three things the mesh now does separately:
| features did | what does it here |
|---|---|
| several deployable units in one thing | several modules, which is what they are |
| turning one on for one node | assignment, which is per node already |
| keeping related things together | requires, and a module with requirements and no files of its own |
networking is exactly that last row: it ships nothing, requires a private network and name
resolution, and assigning it brings both. So the module count does not return, because the thing
that made it return — a module is expensive, so put several things in one — is gone. A module
here is cheap: a manifest and, usually, nothing else.
One file at the root
module.json, and a convention somebody can look for beats a setting somebody has to find. It
says what the module is, what it provides and requires, what it claims, what capabilities it
needs, what it puts on a machine — and, if anything must be produced from the source, what to
build.
The manifest in the repository is not the manifest the mesh holds
A resource names an artifact:
{"id": "dotfiles", "type": "archive", "artifact": "config", "path": "…"}
and the built manifest names the thing:
{"id": "dotfiles", "type": "archive", "source": "…/blobs/sha256:…", "digest": "sha256:…"}
Two documents on purpose. A digest is not knowable until something is built, so a repository carrying one is a repository whose file is wrong the moment anybody edits anything — and the mesh would be pinning a value nobody could have checked. The built manifest is derived, and the record of which commit it was derived from is what makes "is this current?" answerable without building it again.
The word artifact never reaches a machine. The host's decoder is strict and would refuse it, at
the worst possible moment.
The builder runs on a node
Not in the control plane, and this is the same boundary as everywhere else. Building needs a container runtime and a working tree; what the control plane may send a machine is bounded by the declaration language (ADR 0005), and run this build is not in it. The alternative — the control plane holding a container socket — would make it the one component that can do anything on any machine, which is the property the whole design is arranged to avoid.
So the builder is a module a node runs, given work over the broker like anything else. Today it is a command a person runs on such a machine; the mesh records the result identically either way, which is what makes the change from one to the other uninteresting.
Three properties that are decisions
- A fresh clone every time. A build reusing a working tree can succeed because of something a previous build left behind, and that is a build nobody can reproduce.
- Archives are packed deterministically — sorted, and carrying no timestamps, ownership or original paths. Two builds of one commit must produce one digest, or nothing downstream can tell this changed from this was built again, and every rebuild looks like a change to every machine holding it.
- Nothing is published until everything is built. Half a module in the store, under a digest the mesh never records, is reachable, unreferenced, and indistinguishable from something in use.
Where artifacts go
The registry the bootstrap already pulls from, for both images and archives. An OCI registry is a content-addressed blob store that also understands images, and an archive is a content-addressed blob.
An object store beside it is the right answer for objects that are mutable, need per-reader access, or are not build output. None of that describes a digest-pinned archive, and running a second service for one kind of immutable blob is two things to run, two to back up, and two ways for an artifact to be missing. Overturnable without touching anything else: a manifest carries a URL and a digest, and neither says what served it.