Commit Graph
2 Commits
Author SHA1 Message Date
jschoubben 7d033ad9f6 Publish to the registry, and a command that builds a repository
One store, and it is the registry the bootstrap already pulls from. An
OCI registry is a content-addressed blob store that also understands
images: PUT a blob and it is retrievable at /v2/<name>/blobs/sha256:… for
ever, by digest. An archive is a content-addressed blob.

A second store beside it was considered and is the right answer for
objects that are mutable, need per-reader access, or are not build output
— somebody's uploads, a backup, a thing with a lifecycle. None of that
describes a digest-pinned archive, and running a second service to hold
one kind of immutable blob is two things to run, two to back up, and two
ways for an artifact to be missing. Overturnable by reading: the manifest
carries a URL and a digest, and neither says what served it.

`build <repository>` clones, reads module.json, builds what it declares,
publishes, and records the manifest with the commit it came from. It is a
command rather than something the control plane does on its own, because
building runs things on a machine and what the control plane may send a
machine is bounded by the declaration language. This is the shape the
builder module takes when it is given work over the broker.

Proven end to end on a real repository and a real registry: a shell
module with a package, a user and a dotfile archive built, published,
fetched back at the digest it declared, rebuilt to the same digest, and
its manifest accepted by the host's own parser — including `user` and
`archive`, which did not exist this morning.

A tag is never accepted as a pin, and a blob already stored is not sent
again — it is named by its content, so re-uploading asks the registry to
store what it already has under the name it already has.
2026-08-30 03:36:04 +02:00
jschoubben 604b04886b A builder: a repository becomes artifacts the mesh can pin
It runs on a node, not in the control plane. Building needs a container
runtime and a working tree, and the control plane deliberately cannot run
commands on a machine — what it may send is bounded by the declaration
language, and "run this build" is not in it. So the builder is something
a node runs as a module, given work over the broker like anything else.
The alternative, the control plane holding a docker socket, would make it
the one component that can do anything anywhere, which is the property
the whole design is arranged to avoid.

A module repository has one file at its root, module.json, saying what it
is and what it builds. A convention somebody can look for beats a setting
somebody has to find.

Properties that are decisions rather than details:

- 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, uid, gid or original names. 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.

The reproducibility test was passing for the wrong reason: both builds
landed in the same second, so a packer carrying timestamps would still
have agreed. It now stamps the two trees a year apart, and a timestamp in
the header breaks it.

One line is honest about not being independently tested: the sort before
packing is belt and braces over filepath.Walk's documented lexical order,
and no injection can distinguish it.
2026-08-30 03:32:46 +02:00