Files
hq/04-ISSUES/039-the-lab-registry-was-silently-pinning-unpinned-modules/00-report.md
T
jschoubben c29accd1be Issue 039 resolved — the operator's images are pinned, and why that is a stopgap
Nine references now name the digest their tag resolved to. What closed is the
immediate fault; the open questions stand, because a digest in a repository is
wrong the moment anybody rebuilds — which is the reason the design wants the
repository to name artifacts and the mesh to hold digests.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
2026-09-11 01:06:35 +02:00

79 lines
4.4 KiB
Markdown

---
status: resolved
opened: 2026-09-10
located-in: [mesh-catalog]
fixed-by: mesh-catalog — the operator's own images are pinned by digest
amended-design:
---
# 039 — The lab's registry was pinning what the catalogue left unpinned
## Symptom
Nine container images across seven modules name their image by **tag** — the shape
`<registry>/<org>/<name>:latest` — rather than by digest. They are the operator's own application
images, the ones built from their own source.
[ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md) requires a digest, and
the host refuses a tag by name: *"image %q is not pinned. Write it as name@sha256:… — a tag moves,
and a bundle that pinned a tag would not be pinned."*
**Every bed passed anyway, for as long as the lab has existed.** The lab raised a registry of its
own, pushed every image into it, and rewrote every reference in every manifest to the digest **that
registry had just assigned**. So a manifest naming a tag arrived at a machine naming a digest. The
rewriting was doing the pinning.
It surfaced only when the lab's registry was deleted — the modules now carry their tags all the way
to the machine, and fail there, which is the correct behaviour finally being reachable.
## Why this matters
**A rule enforced by scenery is not enforced.** The host's refusal is right and has never once
fired in a bed, because nothing unpinned could reach it. The check exists, the tests are green, and
the property they appear to defend was being supplied by the test harness — which is the same shape
as [003](../003-firewall-scope-is-read-by-no-code/00-report.md), one layer further out: there the
rule was read by no code, here it is read by code that never saw a violation.
**And these are the worst images for it to be true of.** A tag republished on every build is the
one reference that genuinely moves. A machine reconciling against an unchanged declaration can
change what it runs, with nothing in the declaration or the mesh's records saying anything did —
which is precisely the failure pinning exists to prevent, aimed at the images that change most
often.
**The general form is the part worth keeping.** Any invariant the lab happens to satisfy
incidentally is an invariant no bed tests. The harness was not merely serving images; it was
quietly supplying a property of the system under test, and nothing said so.
## Fixed, and what the fix does not settle
Each of the nine references now names the digest its tag resolved to, read from the registry that
serves them. Every manifest in the catalogue is pinned; the host's refusal has nothing left to
catch, and the check that would have caught this — *no manifest names a tag* — now passes on
content rather than on a harness's rewriting.
**It is a stopgap and should be read as one.** A digest written into a repository is wrong the
moment anybody rebuilds, which is exactly the argument
[`12-a-module-repository`](../../03-DESIGN/01-to-be/12-a-module-repository.md) makes for two
documents — the repository naming *artifacts*, the mesh holding *digests*. Until something builds
and publishes, a digest that goes stale is still better than a tag that moves without telling
anyone: stale fails loudly at the pull, and a moved tag changes what a machine runs while every
record says nothing changed.
So the open questions below stand. What closed is the immediate fault; what remains is the reason
it was possible.
## Open questions
- What should a manifest name for an image the operator builds themselves? A digest changes on
every build, so a manifest carrying one is wrong the moment anybody commits — which is the
argument [`12-a-module-repository`](../../03-DESIGN/01-to-be/12-a-module-repository.md) already
makes for *two* documents, the repository's naming artifacts and the mesh's naming digests. Is
this simply the build pipeline's absence showing, rather than seven mistakes?
- Until that pipeline exists, what names these images — and is a tag with a loud warning better or
worse than a digest that is stale by construction?
- How is *"nothing reaches a machine unpinned"* checked anywhere other than on the machine that
refuses it? A check that only ever runs at the last possible moment, on the one path a harness
was rewriting, is a check nobody can see failing.
- Which other invariants is the lab supplying rather than testing? This one was found by deleting
the thing that supplied it. That is not a repeatable technique.