The playbook, the README and the status skill knew five statuses; the cycle check knew a sixth, 'fixed', and not 'wontfix'. Eleven issues sat in the sixth for weeks with their fixes shipped, one step short of closed. They are resolved; the check refuses the word from now on and accepts the one the playbook allows.
115 lines
5.3 KiB
Markdown
115 lines
5.3 KiB
Markdown
---
|
|
status: resolved
|
|
opened: 2026-08-28
|
|
located-in: [mesh-lab, mesh-host]
|
|
fixed-by:
|
|
- "mesh-lab: a registry raised inside the scenario. Verified in a sealed machine — all four shapes applied with the image pinned by digest, idempotent, read back from the machine."
|
|
amended-design:
|
|
---
|
|
|
|
# 009 — A digest-pinned image cannot be placed in the lab, so `container` cannot be tested there
|
|
|
|
## Symptom
|
|
|
|
Two accepted decisions collide, and the collision makes one resource shape untestable.
|
|
|
|
- **[ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)** pins images by
|
|
digest, and the host **refuses** an image reference that is not pinned:
|
|
|
|
```
|
|
resource "store": image "alpine:3.20" is not pinned. Write it as name@sha256:...
|
|
```
|
|
|
|
- **The lab cannot place a digest-pinned image.** A sealed scenario cannot reach a registry, so
|
|
the lab exports an image from the workstation and loads it in the machine — and that loses the
|
|
digest.
|
|
|
|
So a `container` resource is refused by the host if it names a tag, and unusable if it names a
|
|
digest. **There is no declaration the lab can currently raise that exercises the shape.**
|
|
|
|
## What was measured
|
|
|
|
Not inferred. `docker save alpine@sha256:d9e8…` produces an archive with **no repo tag**, because
|
|
a repo digest exists only for an image a registry served. Loading it says:
|
|
|
|
```
|
|
Loaded image ID: sha256:63f227… (not "Loaded image: alpine:3.20")
|
|
```
|
|
|
|
and `docker images` then lists nothing — the image is there but dangling. A container declaring
|
|
that digest therefore falls through to the registry:
|
|
|
|
```
|
|
Unable to find image 'alpine@sha256:d9e8…' locally
|
|
dial tcp: lookup registry-1.docker.io: no such host
|
|
```
|
|
|
|
which is correct behaviour on a machine with no route out.
|
|
|
|
## What is not affected
|
|
|
|
Everything else placed in the same sealed machine works, and was verified there:
|
|
|
|
| shape | |
|
|
|---|---|
|
|
| `package` | applied, idempotent |
|
|
| `service` incl. `boot: enabled` | applied, read back as `enabled` |
|
|
| `action` | ran, verified |
|
|
| `container` | **blocked by this issue** |
|
|
|
|
## Why it matters more than one shape
|
|
|
|
The container shape is the substrate. Every step of raising a mesh past the container runtime is
|
|
a container ([`07-the-foundation.md`](../../03-DESIGN/01-to-be/07-the-foundation.md)), so the
|
|
bootstrap cannot be tested end-to-end until this is resolved — which is the thing the lab exists
|
|
for.
|
|
|
|
## How it was fixed
|
|
|
|
*2026-08-29.* A registry inside the scenario, as below — and it turned out to be the shape the
|
|
resolution predicted rather than a compromise on it.
|
|
|
|
A scenario declares `images:` by tag. The lab stocks a registry **on the workstation**, where
|
|
there is a network, then raises one **inside the scenario** as scenery and serves them from it.
|
|
What a declaration pins is reported when the scenario is raised, because the digest belongs to
|
|
that registry and is not knowable before it exists.
|
|
|
|
**The digests are the lab registry's own, and that is correct rather than a workaround.** What
|
|
[ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md) requires is a
|
|
reference that is exact and cannot move. A digest this registry assigned is both.
|
|
|
|
Verified in a machine confirmed to have no route out: `package`, `service` including boot state,
|
|
a `container` pinned by digest, and an `action` inside that container — applied, idempotent on
|
|
re-apply, and read back from the machine rather than from the apply's own report.
|
|
|
|
**One fault is worth keeping**, because it is this repository's own subject arriving in the
|
|
tooling built to catch it. The read-back checked that the registry's catalog endpoint answered,
|
|
by looking for the substring `repositories` — which `{"repositories":[]}` also contains. So it
|
|
**passed on a registry holding nothing**, and the failure surfaced much later as a container that
|
|
could not be pulled, a long way from its cause. It now asks for each image's manifest **by
|
|
digest**, which is what a machine actually does.
|
|
|
|
## The shape of a resolution
|
|
|
|
**A registry inside the scenario**, on its public segment, that machines pull from. That is not a
|
|
workaround: it is what the real mesh does — [ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)
|
|
names an OCI registry as substrate, and every node after the first pulls from the mesh's own.
|
|
Testing against a registry is testing the real path rather than a stand-in for it.
|
|
|
|
It also removes the lab's export-and-push mechanism rather than fixing it, which is the better
|
|
outcome: pushing image tarballs over the hypervisor was always a lab-only invention.
|
|
|
|
**Not decided here**, because it is design rather than repair: where the registry runs, whether
|
|
it is scenery like the router ([ADR 0016](../../02-DECISIONS/0016-the-lab.md))
|
|
or a placed artifact, and how images get into it.
|
|
|
|
## Incidental, and already fixed
|
|
|
|
The lab's own check on the load was too weak: it matched `"Loaded image"`, which is a prefix of
|
|
both `Loaded image:` and `Loaded image ID:`. So a load that produced an unusable dangling image
|
|
**reported success**, and the failure surfaced later as a container that would not start. It now
|
|
matches `Loaded image:` exactly and says what the runtime actually said.
|
|
|
|
That is this repository's own subject arriving in its own tooling: a check that passes on the
|
|
wrong thing is worse than no check, because it moves the failure away from its cause.
|