ADR 0097: a vendor image is a declared build input; issue 064's image half decided; design 18 amended

This commit is contained in:
2026-09-21 20:45:36 +02:00
parent 8d981e21b1
commit 71072e240d
4 changed files with 74 additions and 2 deletions
@@ -0,0 +1,59 @@
---
topic: building it
status: accepted
date: 2026-09-21
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0096-an-upstream-image-is-copied-between-registries.md
---
# 97. A vendor image is a declared build input, and a recipe fetches nothing undeclared
## Context
Three modules could not be built by the mesh's builder because their recipes reached for what
no manifest named: a public package, or a binary copied out of a public image
([issue 064](../04-ISSUES/064-a-mesh-build-cannot-fetch-a-modules-external-dependencies/00-report.md)).
A module already names the bases it stands on — another module's artifact, by name — and the
builder answers with what the mesh holds; a vendor's image had no such declaration, so a recipe
named it directly and the build worked when the public registry answered, which is sometimes.
## Considered Options
1. **Let the build environment reach public registries.** Rejected: a build that fetches from
somebody else's registry on its own is one the mesh cannot rebuild the same way twice.
2. **A vendor image is a base like any other**, declared under `build.on` with the argument the
recipe reads it from, pinned by digest, copied into the mesh's registry before the build
([ADR 0096](0096-an-upstream-image-is-copied-between-registries.md)). Adopted.
## Decision
A build's `on` entry is either a module's artifact or an image published elsewhere, pinned by
digest, read from one build argument. Before the build the image is copied into the mesh's
registry under the module's repository and the recipe is handed the copy; genesis, with no
registry, pulls it into the first machine's store. A recipe whose `FROM` or `COPY --from` names a
registry image the manifest did not declare is refused before the build, naming the image and
the remedy; its own stages, declared arguments and `scratch` are not fetches. An unpinned vendor
image is refused: a tag is what somebody else can move.
The package half of the issue is not decided here: the mesh's package registry already proxies
the public one, and the failure the report saw has to be run again to be placed.
## Consequences
A module's build inputs are all in its manifest, and every one of them is something the mesh
holds a copy of. What got harder: a recipe that used to name a base image on its first line now
names an argument, and the manifest names the image.
## How it is checked
A builder test declares a pinned vendor image, asserts it is copied under the module's
repository and handed to the recipe as the argument, and asserts an unpinned one is refused. A
recipe test asserts an undeclared `FROM`, an undeclared `COPY --from` and an undeclared argument
are named, and that stages, declared arguments and `scratch` are not.
## References
- [issue 064](../04-ISSUES/064-a-mesh-build-cannot-fetch-a-modules-external-dependencies/00-report.md)
- [ADR 0096](0096-an-upstream-image-is-copied-between-registries.md)
- [`03-DESIGN/01-to-be/18-building-a-module.md`](../03-DESIGN/01-to-be/18-building-a-module.md)
+1
View File
@@ -162,6 +162,7 @@ python3 00-META/checks/index.py fail if stale
- **0082** — [The registry is reached by name, and the overlay is its security](0082-the-registry-is-reached-by-name-and-trusted-by-the-overlay.md)
- **0086** — [A secret reaches a process as a file, and an exception is declared](0086-a-secret-reaches-a-process-as-a-file.md)
- **0096** — [An upstream image is copied between registries, never through a machine's image store](0096-an-upstream-image-is-copied-between-registries.md)
- **0097** — [A vendor image is a declared build input, and a recipe fetches nothing undeclared](0097-a-vendor-image-is-a-declared-build-input.md)
### How it is checked
@@ -7,6 +7,7 @@ code:
- mesh-catalog modules/builder
updated: 2026-09-21
decisions:
- 02-DECISIONS/0097-a-vendor-image-is-a-declared-build-input.md
- 02-DECISIONS/0096-an-upstream-image-is-copied-between-registries.md
- 02-DECISIONS/0091-a-mount-is-declared-three-ways.md
- 02-DECISIONS/0087-a-seeded-file-is-created-once.md
@@ -215,6 +216,14 @@ test copies an index over two platforms from a fake registry behind a bearer cha
mesh registry and asserts every blob arrived once, the manifests and index under their digests,
and nothing uploaded on a second copy.
**A vendor image is a declared build input**
([ADR 0097](../../02-DECISIONS/0097-a-vendor-image-is-a-declared-build-input.md)). A build's `on`
entry is a module's artifact or an image published elsewhere, pinned by digest, read from one
build argument; the image is copied into the mesh's registry before the build and the recipe is
handed the copy. A recipe whose `FROM` or `COPY --from` names a registry image the manifest did not
declare is refused before the build, naming it and the remedy. *How it is checked:* builder tests
on a declared and an unpinned vendor image, and a recipe test on what counts as a fetch.
### What it puts on a machine
| resource | is | a module may |
@@ -16,5 +16,8 @@
repositories succeed in the same Dockerfiles.
**Located in:** the builder (what it tells npm and the runtime) and the catalogue (three modules
whose Dockerfiles fetch what no manifest names). Still open: a build must be re-run to place the
404, and a decision is needed on whether a vendor image is declared as a build input.
whose Dockerfiles fetch what no manifest names). The image half is decided and built:
[ADR 0097](../../02-DECISIONS/0097-a-vendor-image-is-a-declared-build-input.md) — a vendor image
is declared under `build.on`, copied in, and a recipe fetching what is undeclared is refused. Still
open: the package half — a build must be re-run against the mesh's proxying registry to place the
404 — and the three recipes themselves, which now declare their images or are refused.