fix(release): link the image and chart to the repo on GHCR (ADR-0020) #38

Merged
ryangr0 merged 2 commits from fix/link-packages-to-repo into development 2026-08-25 15:05:03 +00:00
Owner

Neither published package is connected to a repository on GHCR, for two different reasons. Both are one line of metadata — plus the record explaining why the value is what it is.

GHCR links a package to a repository by matching org.opencontainers.image.source against a github.com URL.

declared why it never linked
image https://forgejo.webgrip.dev/webgrip/ploeg matches no GitHub repo
chart nothing Helm derives the annotation from Chart.yaml sources[0], and there was no sources field

The decision, not just the fix — ADR-0020

The value change looks like an inconsistency until explained, which is precisely what ADR-0004 said about the module path. So this is 0004's asymmetry applied to a second artifact class:

the module path names the mirror because Go resolves modules over a public host; published artifacts name the mirror because OCI consumers resolve source over a public host.

Forgejo is not demoted — it moves to image.url, "URL to find more information on the image", which is where a canonical home belongs. The governance claim lives in the ledger, not in a label.

The record captures the constraint that removed most of the option space — the image is built once and digest-copied, so one label serves all three registries; per-registry values would mean per-registry builds and the loss of identical-digest mirroring — and weighs the three rejected alternatives (keep Forgejo + link by hand, add a bespoke canonical-source label, stop publishing to GHCR).

Two facts that settled it, both checked rather than argued:

  • the commit an image was built from resolves on the mirror (revision 0225131… → HTTP 200), and all 38 tags are present, so the annotation's claim is true for every holder of the artifact
  • forgejo.webgrip.dev does not resolve off the VPN, so the old value was a dead hostname for the entire audience GHCR exists to serve
  • nothing in the estate consumes the annotation (searched; one comment in docker-mirror.yml, no consumer)

Confirmation is a gate, not a promise

Verify image metadata labels now asserts image.source against EXPECTED_SOURCE for every platform, so a regression to the Forgejo value fails the Harbor job instead of silently orphaning the packages again. Exercised against a correct image (passes) and one carrying the old value (fails, naming the offending platform).

Helm's Chart.yaml → OCI annotation mapping was verified by pushing the packaged chart to a throwaway local registry and reading the manifest back:

org.opencontainers.image.source = https://github.com/webgrip/ploeg
org.opencontainers.image.url    = https://forgejo.webgrip.dev/webgrip/ploeg

Gates

go build / vet / test ./... green — including internal/ledger, the ADR consistency validator — gofmt clean, helm lint and all three chart renderings.

Two caveats

  • Linking applies to versions pushed after this ships. Linking is package-level, so one correct push links the package and rc.25/rc.26 sit under it — no cleanup needed.
  • The packages must be public before the link is visible to anyone not signed in. That flip is UI-only and still outstanding; it also blocks staging from pulling at all.
Neither published package is connected to a repository on GHCR, for two different reasons. Both are one line of metadata — plus the record explaining why the value is what it is. GHCR links a package to a repository by matching `org.opencontainers.image.source` against a **github.com** URL. | | declared | why it never linked | |---|---|---| | image | `https://forgejo.webgrip.dev/webgrip/ploeg` | matches no GitHub repo | | chart | *nothing* | Helm derives the annotation from `Chart.yaml` `sources[0]`, and there was no `sources` field | ## The decision, not just the fix — ADR-0020 The value change looks like an inconsistency until explained, which is precisely what **ADR-0004** said about the module path. So this is 0004's asymmetry applied to a second artifact class: > the module path names the mirror because Go resolves modules over a public host; published artifacts name the mirror because OCI consumers resolve source over a public host. Forgejo is not demoted — it moves to `image.url`, *"URL to find more information on the image"*, which is where a canonical home belongs. The governance claim lives in the ledger, not in a label. The record captures the constraint that removed most of the option space — **the image is built once and digest-copied, so one label serves all three registries**; per-registry values would mean per-registry builds and the loss of identical-digest mirroring — and weighs the three rejected alternatives (keep Forgejo + link by hand, add a bespoke `canonical-source` label, stop publishing to GHCR). Two facts that settled it, both checked rather than argued: - the commit an image was built from **resolves on the mirror** (`revision 0225131… → HTTP 200`), and all 38 tags are present, so the annotation's claim is true for every holder of the artifact - `forgejo.webgrip.dev` does not resolve off the VPN, so the old value was a **dead hostname** for the entire audience GHCR exists to serve - nothing in the estate consumes the annotation (searched; one comment in `docker-mirror.yml`, no consumer) ## Confirmation is a gate, not a promise `Verify image metadata labels` now asserts `image.source` against `EXPECTED_SOURCE` for every platform, so a regression to the Forgejo value **fails the Harbor job** instead of silently orphaning the packages again. Exercised against a correct image (passes) and one carrying the old value (fails, naming the offending platform). Helm's `Chart.yaml` → OCI annotation mapping was verified by pushing the packaged chart to a throwaway local registry and reading the manifest back: ``` org.opencontainers.image.source = https://github.com/webgrip/ploeg org.opencontainers.image.url = https://forgejo.webgrip.dev/webgrip/ploeg ``` ## Gates `go build` / `vet` / `test ./...` green — including `internal/ledger`, the ADR consistency validator — `gofmt` clean, `helm lint` and all three chart renderings. ## Two caveats - Linking applies to versions pushed **after** this ships. Linking is package-level, so one correct push links the package and rc.25/rc.26 sit under it — no cleanup needed. - The packages must be **public** before the link is visible to anyone not signed in. That flip is UI-only and still outstanding; it also blocks staging from pulling at all.
fix(release): link the image and chart to the repo on GHCR
All checks were successful
On Pull Request / checks (pull_request) Successful in 53s
1440917b6e
Neither published package is connected to a repository on GHCR, for two
different reasons, and both are one line of metadata.

GHCR links a package to a repository by matching the OCI annotation
org.opencontainers.image.source against a github.com URL. The image declared
"https://forgejo.webgrip.dev/webgrip/ploeg", which matches nothing on GitHub,
and the chart declared no source at all — Helm derives that annotation from
Chart.yaml sources[0], and there was no sources field.

image.source now points at the github.com mirror, which is the URL GHCR can
resolve and the one a stranger can actually open. The canonical home moves to
image.url, so Forgejo being the source of truth is still stated, just in the
field that means "where to find out more" rather than the one GitHub matches
on. Same split for the chart: sources[0] is the mirror, home is Forgejo.

Verified by pushing the packaged chart to a throwaway local registry and
reading the manifest back, rather than assuming Helm's mapping:

    org.opencontainers.image.source = https://github.com/webgrip/ploeg
    org.opencontainers.image.url    = https://forgejo.webgrip.dev/webgrip/ploeg

Linking applies to versions pushed AFTER this ships; rc.25 and earlier stay
orphaned, and the packages must also be public before the link is visible to
anyone not signed in.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ryangr0 changed title from fix(release): link the image and chart to the repo on GHCR to fix(release): link the image and chart to the repo on GHCR (ADR-0020) 2026-08-25 15:02:56 +00:00
docs(adr): record why published artifacts name the mirror as their source
All checks were successful
On Pull Request / checks (pull_request) Successful in 1m24s
e509e22649
ADR-0020. The Dockerfile and Chart.yaml changes in the previous commit make a
claim about which forge an artifact points at, and that claim deserves to be
argued somewhere other than a commit message — it looks like an inconsistency
until explained, which is exactly what 0004 said about the module path.

This is 0004's asymmetry applied to a second artifact class: the module path
names the mirror because Go resolves modules over a public host; published
artifacts name the mirror because OCI consumers resolve source over a public
host. Same reason, same accepted trade-off. Forgejo is not demoted, it moves to
image.url, and the governance claim lives in the ledger rather than in a label.

The record also captures the constraint that removed most of the option space —
the image is built once and digest-copied, so one label serves all three
registries — and the two alternatives that were weighed and rejected.

The Confirmation is now a real gate rather than a promise: the release
workflow's `Verify image metadata labels` step asserts image.source against
EXPECTED_SOURCE for every platform, so a regression to the Forgejo value fails
the Harbor job instead of silently orphaning the GHCR packages again. Exercised
against a correct image (passes) and one carrying the old value (fails, and
names the offending platform).

Gates: go build/vet/test ./... green including internal/ledger (the ADR
consistency validator), gofmt clean, helm lint and all three renderings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ryangr0 merged commit add1aafa3c into development 2026-08-25 15:05:03 +00:00
Commenting is not possible because the repository is archived.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
webgrip/ploeg!38
No description provided.