Scope the release signal, collapse the contract onto the release version, publish the Hub overview #83

Merged
lilleman merged 17 commits from auto-release-only-when-affected into main 2026-08-22 22:19:59 +02:00
Owner

Four related changes to the release pipeline, all downstream of scoping Release-Bump.

Release nothing when no Renovate commit reached the app
auto-release fired on any Renovate commit since the last tag, and with no trailers maxLevel([]) falls back to patch — so Renovate updating its own image cut a version, publishing an image whose only difference is a workflow file nothing reads at runtime. The job now skips when nothing since the last tag carried a trailer.

The trailer itself is an allowlist over the surfaces that reach a running Plainpages. The production sidecars are scoped by package, not by file: compose.yml and the published quick start pin the same images, Renovate moves them in one branch, and a branch-level commitBody would otherwise depend on which upgrade sorted first — silently suppressing a release.

One version number
HOST_API_VERSION is the Plainpages release version: a plugin author reads one version off the image they run and writes it down. release-tooling/contract-version.ts is the gate — both release paths refuse a tag whose major.minor disagrees with the constant, naming the value to set.

The patch digit may lag deliberately: checkApiVersion ignores patch, and auto-release cuts patch releases with no commit in which to bump a constant. An update big enough to force a minor therefore stops auto-release rather than tagging, and a human bumps the constant first. Promote-by-retag is untouched — the released image stays byte-identical to the gated one.

Pre-1.0 the major is pinned at 0, which leaves the minor as the only slot a breaking change can use, so a minor mismatch now refuses instead of warning. A plugin that is ahead of the host keeps its own remedy ("upgrade the host") rather than being told to rebuild against the older contract. For the same reason the moving bare-major tag is no longer published while the major is 0: :0 would carry an adopter across a contract break. :0.1 stays, since it only moves across patches.

A test pins every author-facing apiVersion sample to the constant, so the copies in the README, both example plugins, the dashboard view and the Hub template cannot drift from it.

The Docker Hub overview is published by CI
release-tooling/dockerhub-overview.md.tmpl carries {{VERSION}}, rendered from the release and PATCHed to Docker Hub, so the Plainpages tag the page tells adopters to pull cannot go stale. Its sidecar pins are Renovate-managed and gated against this repo's own compose files, so the quick start stays a topology CI has actually run. A leftover placeholder fails the job rather than publishing literal braces to a public page.

It is a separate publish-overview job, not a step: the images are already pushed and irreversible by then, so a Hub outage leaves the promotion green and the images untouched. workflow_dispatch gives the page its own door — a stale page is exactly the state you cannot fix by cutting a release — and it checks out the named release's own tag, so a republish never pairs one Plainpages version with another release's sidecars, and a version that was never released fails at checkout.

It uses DOCKERHUB_OVERVIEW_TOKEN, separate from the image-push token because editing repository metadata is a different permission and widening the push credential to cover it would widen what a leak costs.

The template is deliberately not a .md: ci.sh skips the whole gate on a docs-only change, so a .md name would let a broken placeholder merge with its own guard test skipped.

Also

  • release-tooling/ holds everything the release runs — the bump math, the contract gate and the Hub publisher.
  • .gitea/workflows/release.yml joins the node custom manager's file patterns; it pins node and nothing watched it.
  • The host logs its apiVersion on boot.
  • The version field is gone from package.json and e2e-tests/package.json — both private, unpublished, read by nothing, and never written back to by the release path.

Needs before merge: create the DOCKERHUB_OVERVIEW_TOKEN secret (README → One-time CI setup). Without it the publish-overview job fails; the released images are unaffected.

Four related changes to the release pipeline, all downstream of scoping `Release-Bump`. **Release nothing when no Renovate commit reached the app** `auto-release` fired on any Renovate commit since the last tag, and with no trailers `maxLevel([])` falls back to `patch` — so Renovate updating its own image cut a version, publishing an image whose only difference is a workflow file nothing reads at runtime. The job now skips when nothing since the last tag carried a trailer. The trailer itself is an allowlist over the surfaces that reach a running Plainpages. The production sidecars are scoped by **package**, not by file: `compose.yml` and the published quick start pin the same images, Renovate moves them in one branch, and a branch-level `commitBody` would otherwise depend on which upgrade sorted first — silently suppressing a release. **One version number** `HOST_API_VERSION` is the Plainpages release version: a plugin author reads one version off the image they run and writes it down. `release-tooling/contract-version.ts` is the gate — both release paths refuse a tag whose `major.minor` disagrees with the constant, naming the value to set. The patch digit may lag deliberately: `checkApiVersion` ignores patch, and auto-release cuts patch releases with no commit in which to bump a constant. An update big enough to force a **minor** therefore stops `auto-release` rather than tagging, and a human bumps the constant first. Promote-by-retag is untouched — the released image stays byte-identical to the gated one. Pre-1.0 the major is pinned at `0`, which leaves the minor as the only slot a breaking change can use, so a minor mismatch now **refuses** instead of warning. A plugin that is *ahead* of the host keeps its own remedy ("upgrade the host") rather than being told to rebuild against the older contract. For the same reason the moving bare-major tag is no longer published while the major is `0`: `:0` would carry an adopter across a contract break. `:0.1` stays, since it only moves across patches. A test pins every author-facing `apiVersion` sample to the constant, so the copies in the README, both example plugins, the dashboard view and the Hub template cannot drift from it. **The Docker Hub overview is published by CI** `release-tooling/dockerhub-overview.md.tmpl` carries `{{VERSION}}`, rendered from the release and PATCHed to Docker Hub, so the Plainpages tag the page tells adopters to pull cannot go stale. Its sidecar pins are Renovate-managed and gated against this repo's own compose files, so the quick start stays a topology CI has actually run. A leftover placeholder fails the job rather than publishing literal braces to a public page. It is a separate `publish-overview` job, not a step: the images are already pushed and irreversible by then, so a Hub outage leaves the promotion green and the images untouched. `workflow_dispatch` gives the page its own door — a stale page is exactly the state you cannot fix by cutting a release — and it checks out the named release's own tag, so a republish never pairs one Plainpages version with another release's sidecars, and a version that was never released fails at checkout. It uses `DOCKERHUB_OVERVIEW_TOKEN`, separate from the image-push token because editing repository metadata is a different permission and widening the push credential to cover it would widen what a leak costs. The template is deliberately not a `.md`: `ci.sh` skips the whole gate on a docs-only change, so a `.md` name would let a broken placeholder merge with its own guard test skipped. **Also** - `release-tooling/` holds everything the release runs — the bump math, the contract gate and the Hub publisher. - `.gitea/workflows/release.yml` joins the node custom manager's file patterns; it pins `node` and nothing watched it. - The host logs its `apiVersion` on boot. - The `version` field is gone from `package.json` and `e2e-tests/package.json` — both private, unpublished, read by nothing, and never written back to by the release path. **Needs before merge:** create the `DOCKERHUB_OVERVIEW_TOKEN` secret (README → One-time CI setup). Without it the `publish-overview` job fails; the released images are unaffected.
lilleman added 1 commit 2026-08-20 23:02:28 +02:00
lilleman added 1 commit 2026-08-20 23:16:14 +02:00
lilleman changed title from Release nothing when no Renovate commit reached the app to Collapse the plugin contract onto the release version, and stop releasing what never reached the app 2026-08-20 23:16:32 +02:00
lilleman added 1 commit 2026-08-20 23:32:12 +02:00
lilleman added 1 commit 2026-08-22 11:21:43 +02:00
lilleman added 1 commit 2026-08-22 11:24:18 +02:00
Trim the prose to what is true now
CI / full-gate (push) Successful in 2m51s
5820264885
lilleman changed title from Collapse the plugin contract onto the release version, and stop releasing what never reached the app to Scope the release signal, collapse the contract onto the release version, publish the Hub overview 2026-08-22 11:24:49 +02:00
lilleman added 1 commit 2026-08-22 11:27:37 +02:00
lilleman added 1 commit 2026-08-22 11:45:47 +02:00
lilleman added 1 commit 2026-08-22 11:58:30 +02:00
lilleman added 1 commit 2026-08-22 12:01:37 +02:00
lilleman added 1 commit 2026-08-22 12:03:20 +02:00
lilleman added 1 commit 2026-08-22 12:13:43 +02:00
lilleman added 1 commit 2026-08-22 12:27:46 +02:00
lilleman added 1 commit 2026-08-22 14:21:15 +02:00
Name the scope the overview actually needs
CI / full-gate (push) Successful in 2m51s
734cdf6892
lilleman added 1 commit 2026-08-22 14:27:33 +02:00
lilleman added 1 commit 2026-08-22 21:53:55 +02:00
lilleman added 1 commit 2026-08-22 21:58:15 +02:00
lilleman added 1 commit 2026-08-22 22:16:50 +02:00
Merge branch 'main' into auto-release-only-when-affected
CI / full-gate (push) Successful in 2m52s
Mirror / github-mirror (push) Successful in 4s
Release / retag-image (push) Successful in 17s
Release / publish-overview (push) Successful in 5s
ba4503b4d1
lilleman merged commit ba4503b4d1 into main 2026-08-22 22:19:59 +02:00
lilleman deleted branch auto-release-only-when-affected 2026-08-22 22:19:59 +02:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: larvit/plainpages#83