Skip to content

feat: resolve a declared registry, then fall back to Docker Hub - #42

Merged
CptSchnitz merged 3 commits into
masterfrom
ticket-23/document-registry-and-docker-hub-fallback
Sep 22, 2026
Merged

CptSchnitz merged 3 commits into
masterfrom
ticket-23/document-registry-and-docker-hub-fallback

Conversation

@CptSchnitz

Copy link
Copy Markdown
Collaborator

References that are not fully qualified start working. repository: my-service under a global.imageRegistry is checked against the registry the chart declares, and a bare nginx verifies against Docker Hub with no setup at all. Before this, both came back unverifiable with a shrug.

Resolution runs in three steps: a host the repository names itself, else the registry declared elsewhere in the document, else Docker Hub. The third step is a guess, and a guess is not allowed to accuse — a not-found from the Hub fallback is downgraded to unverifiable, because a bare internal service name is the ordinary reference in this organisation's charts and Hub has never heard of any of them. A positive answer from the guess still stands, so public images keep their checkmark. The downgrade sits on the single verdict the entry point returns rather than on each 404 branch, so no later edit to those branches can make "we guessed the registry" and "we are confident the image is missing" hold at once.

The Helm package emits the declared registry as a raw string and interprets nothing; the registry package decides what a host means, per the ticket. Which keys count as a declaration is a precedence-ordered table — global.imageRegistry, global.registry, imageRegistry, registry, with a sibling registry key beating all of them — so the next convention somebody's charts follow is a new row rather than a new branch.

no-registry is gone, since every repository now resolves to something. It is replaced by two reasons, because it conflated them: guessed-registry is the downgrade above, and malformed-reference widens from the tag to the whole reference.

Reviewer notes

  • library/ normalization applies wherever the host is Hub, not only on the fallback. docker.io/nginx needs it too: the distribution API serves only the long form and answers nginx with a 404 that reads exactly like a missing image.
  • A declared registry that is not a host yields malformed-reference rather than falling through to Hub. Falling through would answer a question about a registry nobody asked about. An empty declared registry is different — it is a caller reading a half-typed file saying "nothing declared" — and falls back.
  • The last commit is review fallout. The one behaviour fix in it is the checkmark: it appends the answering registry only when that differs from what the file names, and until this branch "what the file names" was only ever the repository string. Every bare repository under a declared registry would have had it restated back.

Known gap

A Harbor-style registry: myreg.example.com/project fails the host grammar and comes back malformed-reference, so those references go unverified rather than wrong. Filed as #41.

Closes #23

CptSchnitz and others added 3 commits September 16, 2026 19:03
A values file usually splits the registry off from the repository:
`repository: my-service` under a `global.imageRegistry` names
`myreg.example.com/my-service`, and reading the repository alone gets the
image wrong. Every reference now carries the registry its document declared,
so the registry package has something to resolve a bare name against.

A sibling `registry` key wins, then a document-level one found through a
precedence-ordered table of key paths: `global.imageRegistry`,
`global.registry`, `imageRegistry`, `registry`. A table rather than a chain
of conditionals, because that list is the whole rule — the next convention
somebody's charts follow is a new row, not a new branch.

The declared string is emitted as written, from raw source text with a real
range, exactly as `repository` and `tag` are. Deciding what a host means,
and whether the string even is one, is OCI naming semantics and belongs in
the registry package.

"Templated, non-scalar, or empty counts as absent" was already spelled out
twice and would have been four sites once registry joined, so it folds into
one `readUsableScalar` that every field reads through. A templated sibling
registry therefore falls through to the document-level one instead of
shadowing it, which is what a chart writing
`registry: "{{ .Values.global.registry }}"` means by it.

The extension's test fixtures gain the new field. Nothing in the extension
reads it yet; that arrives with the resolution chain.

Refs #23

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A reference that is not fully qualified now gets checked instead of refused.
Resolution runs in three steps: a host the repository names itself, then the
registry the document declared, then Docker Hub. So `repository: my-service`
under a `global.imageRegistry` is checked where the chart says it lives, and
`nginx` verifies against Hub with no setup at all — with Docker's own
`library/` normalization applied, since the distribution API knows only
`library/nginx` and answers the short form with a 404 that reads exactly
like a missing image.

The third step is a guess, and a guess is not allowed to accuse. A not-found
from the Hub fallback is downgraded to unverifiable, because a bare internal
service name is the ordinary reference in this organisation's charts and Hub
has never heard of any of them, so reporting that as a missing image would
be the false negative this whole feature exists to avoid. A positive answer
from the guess still stands, so public images keep their checkmark. The
downgrade sits on the single verdict the entry point returns rather than on
each 404 branch, so no later edit to those branches can make "we guessed the
registry" and "we are confident the image is missing" hold at once.

`no-registry` is gone: every repository resolves to some registry now, so
nothing reaches it. What replaces it is two reasons rather than one, because
the old one conflated them. `guessed-registry` is the downgrade above.
`malformed-reference` widens from the tag to the whole reference — a
repository name outside the OCI grammar, or a declared registry that is not
a host, is rejected before a request is built. A declared registry that is
not a host is a malformed document, not an invitation to guess Hub instead:
falling through would answer a question about a registry nobody asked about.

`resolve-explicit-host.ts` becomes `resolve-reference.ts`, one module owning
OCI naming. `resolveExplicitHost` keeps its behaviour and its export,
because the extension asks it a different question — what host the file
itself spells out — to decide whether a checkmark should name the registry
that answered. That path is reachable for the first time here, so a bare
`nginx` verified on Hub now says `docker.io` on the line rather than
claiming a registry the file never mentioned.

Closes #23

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`registrySuffixOf` names the answering registry only when it differs from
the one the file names, and it was still asking only what the repository
string spells out — a question this branch had just made the wrong one. A
values file declaring `global.imageRegistry: ghcr.io` got ` ✓ ghcr.io`
appended to every bare repository under it, which is the wallpaper that rule
exists to prevent. The comparison now runs against the registry the file
names by either route. A file naming none still gets Docker Hub spelled out,
because "we guessed" and "you wrote it" are different things and the mark is
where that difference shows.

The rest is review fallout from the same pass.

`documentRegistry` becomes `declaredRegistry`, and its `RegistrySource` arm
`'document'` becomes `'declared'`. ADR 0001 says the registry package knows
nothing about Helm, YAML, or editors, and a document is the caller's idea,
not this package's. No dependency crossed that line, only vocabulary, which
is how it gets crossed for real later.

`ImageReference.registry` narrows from a `RawScalar` to a plain string. Only
its text was ever read, and its range was a hazard rather than a spare
feature: a document-level declaration sits on a line that has nothing to do
with the reference, so the first consumer to underline it would have
underlined `global:` for a diagnostic about an image forty lines down.

`ResolvedReference` extends `RepositoryLocation` instead of respelling its
two fields. The empty-string branch in `selectRegistry` keeps its fallback
and gains the test and the comment it was missing: an empty declared
registry is a caller reading a half-typed file saying "nothing declared",
not a registry named badly, so it falls back rather than failing. A
`{@link UnverifiableReason}` in `check-image-existence.ts` pointed at a
symbol that file does not import, so it resolved to nothing.

Both new assertions were mutation-checked against the code they cover.

Refs #23

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@CptSchnitz
CptSchnitz merged commit 70ef1e3 into master Sep 22, 2026
6 checks passed
@CptSchnitz
CptSchnitz deleted the ticket-23/document-registry-and-docker-hub-fallback branch September 22, 2026 08:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Document registry and the Docker Hub fallback

1 participant