Reconcile: adopt initialization's consolidated HQ as canonical, re-home this session's new work #24
@@ -0,0 +1,97 @@
|
|||||||
|
---
|
||||||
|
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.
|
||||||
Reference in New Issue
Block a user