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
79 lines
4.4 KiB
Markdown
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.
|