--- status: located opened: 2026-09-01 located-in: [mesh-control, mesh-host] fixed-by: partly — mesh-control ee3cc1b amended-design: --- # 025 — A module must pin a digest, and nothing produces one ## Symptom Every image reference in every example module is **sixty-four zeros**: ``` gitea@sha256:0000000000000000000000000000000000000000000000000000000000000000 ``` Eighteen of them, across five modules. Each one parses, resolves, and composes into a declaration a host accepts. None of them could ever start: the machine would reach `docker pull` and stop. This is why those modules are *written* and not *running*, and it was not visible from any check because every check passes. ## Why nothing caught it The host validates the **shape** of a reference and nothing else — that it is `name@sha256:` plus sixty-four hexadecimal characters. Sixty-four zeros satisfies that exactly. That check is not wrong. A host cannot verify a digest exists without reaching a registry, and reaching a registry is precisely what the design refuses to make it do ([ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)). The host is the last place that could catch this and the wrong place to try. ## The actual gap **A manifest must carry a digest, and nothing in the system produces one.** - Images the mesh builds are fine: the bundle writes the digest down *after* building, which is the whole reason the bundle exists in that shape. - Images from anywhere else — a forge, a mail system, a database — have no path at all. Somebody has to look up what `gitea:1.22` points at today and paste it in, and nothing re-checks it. So the design is coherent about *pinning* and silent about *where a pin comes from*. A person writing a module is asked for something they cannot reasonably produce by hand, and given a placeholder shape that passes every gate. ## What a fix has to keep - **The host still refuses a tag.** A digest is what makes a declaration exact, and that must not soften. The fix belongs where a module is added or built, not on the machine. - **A person writes a tag; the mesh holds a digest.** A manifest in a repository naming `gitea:1.22` is readable and reviewable; the mesh resolving that against a registry once, and recording the answer, is what makes it exact. The module table already records this shape for source repositories — where it came from, the branch followed, the commit read — and an image is the same question asked of a registry. - **Re-resolving is a decision, not a side effect.** A tag that moves must not silently change what a machine runs. Whatever resolves it records both, so *this pin is behind its tag* is a question the mesh can answer rather than something discovered on a restart. ## Cheaply, now An all-zero digest is a placeholder and never a real image. Refusing it costs three lines and would have caught all eighteen the day they were written. It does not fix the gap; it stops the gap being invisible. ## What this blocks Every module that names a third-party image, which is every module that is not the mesh itself. The forge and the mail system are otherwise ready to run. ## Half of it is done **A placeholder can no longer reach a machine.** The refusal sits where a declaration is composed, not where a manifest is parsed — a file awaiting a pin is legitimate, and the design already says so for artifacts the mesh builds. Composing is the last moment before a machine sees it. **The examples now pin images that exist.** Twelve third-party digests were resolved against their registries without pulling anything, which is also the mechanism the rest of this issue needs: `docker manifest inspect --verbose` answers *what does this tag point at* in about a second. Two faults came free, and both had been invisible for the same reason as the digests: the mail system's seven images named repositories that **do not exist** — it publishes to a different registry entirely — and one of the seven had been renamed upstream. Nothing that only checks the shape of a reference could ever have found either. ## What is still open **Nothing turns a tag into a digest as part of the mesh's own work.** It was done by hand here, which is exactly what this issue says a person should not be asked to do. The shape of the answer is unchanged: a person writes a tag, the mesh resolves it once and records both, and *this pin is behind its tag* becomes a question the mesh can answer. **The mesh's own provisioner images still cannot be pinned in a repository**, because their digest does not exist until they are built and pushed. That is the bundle's problem, and the bundle solves it by writing the digest down after building. A module naming an image the mesh builds needs the same treatment, and does not have it.