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
4 changed files with 23 additions and 7 deletions
Showing only changes of commit 2d62553a10 - Show all commits
+5 -1
View File
@@ -5,7 +5,7 @@ on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
overview_version: overview_version:
description: 'Version the overview should tell adopters to pull (e.g. 0.1.0)' description: 'Released version to republish the overview for, without the leading v (e.g. 0.1.0)'
required: true required: true
jobs: jobs:
@@ -72,7 +72,11 @@ jobs:
needs: [retag-image] needs: [retag-image]
runs-on: docker-host runs-on: docker-host
steps: steps:
# Publish the named release's own tree, so the page never pairs one Plainpages tag with another
# release's sidecar pins. A version that was never released fails here.
- uses: actions/checkout@v7.0.1 - uses: actions/checkout@v7.0.1
with:
ref: ${{ inputs.overview_version && format('refs/tags/v{0}', inputs.overview_version) || github.ref }}
- name: Publish the Docker Hub overview - name: Publish the Docker Hub overview
env: env:
DOCKERHUB_OVERVIEW_TOKEN: ${{ secrets.DOCKERHUB_OVERVIEW_TOKEN }} DOCKERHUB_OVERVIEW_TOKEN: ${{ secrets.DOCKERHUB_OVERVIEW_TOKEN }}
+9 -6
View File
@@ -607,17 +607,20 @@ provider/consumer semantics in `checkApiVersion`:
| --- | --- | --- | | --- | --- | --- |
| same major, same minor (patch ignored) | `ok` | load | | same major, same minor (patch ignored) | `ok` | load |
| same major, plugin minor **<** host minor | `warn` | load, log — built against an older release; check that release's notes | | same major, plugin minor **<** host minor | `warn` | load, log — built against an older release; check that release's notes |
| **major `0`**, any minor mismatch | `refuse` | **abort boot** — pre-1.0 the minor is the breaking slot |
| same major, plugin minor **>** host minor | `refuse` | **abort boot** — plugin needs a newer host | | same major, plugin minor **>** host minor | `refuse` | **abort boot** — plugin needs a newer host |
| different major | `refuse` | **abort boot** — incompatible contract | | different major | `refuse` | **abort boot** — incompatible contract |
| missing / not a valid semver | `refuse` | **abort boot** — must be declared | | missing / not a valid semver | `refuse` | **abort boot** — must be declared |
The plugin pins one exact version (no ranges, per the project's pinning rules); the *host* supplies The plugin pins one exact version (no ranges, per the project's pinning rules); the *host* supplies
the caret-style compatibility. One digit carries the whole release, so a **minor** means either the the compatibility. One digit carries the whole release, so a **minor** means either the plugin
plugin contract changed or a dependency moved far enough to warrant one. A `warn` therefore says contract changed or a dependency moved far enough to warrant one.
"built against an older release" rather than promising new plugin features; check that release's
notes before assuming there is anything to adopt. While Plainpages is `0.x` every release shares major `0`, so a plugin While Plainpages is `0.x` the major stays `0`, which leaves the minor as the only slot a breaking
built against `0.1.0` still loads on a `0.9.0` host with a `warn`; reaching `1.0.0` refuses everything change can use — so a minor mismatch is refused rather than warned: a plugin declaring `0.1.0` will
built against `0.x`, which is the point of that milestone. not boot on a `0.2.0` host until it is rebuilt against it. Once `1.0.0` lands the minor becomes
additive and the `warn` row applies, saying "built against an older release" rather than promising
new features.
### Conflict rules ### Conflict rules
+3
View File
@@ -86,6 +86,9 @@ test("checkApiVersion: semver compat — equal/patch ok, older minor warns, newe
assert.equal(checkApiVersion("1.3.0", "1.2.0").level, "refuse"); // needs features a newer host has assert.equal(checkApiVersion("1.3.0", "1.2.0").level, "refuse"); // needs features a newer host has
assert.equal(checkApiVersion("2.0.0", "1.5.0").level, "refuse"); // incompatible major (newer) assert.equal(checkApiVersion("2.0.0", "1.5.0").level, "refuse"); // incompatible major (newer)
assert.equal(checkApiVersion("1.0.0", "2.0.0").level, "refuse"); // incompatible major (older) assert.equal(checkApiVersion("1.0.0", "2.0.0").level, "refuse"); // incompatible major (older)
assert.equal(checkApiVersion("0.1.0", "0.1.9").level, "ok"); // pre-1.0 patch is still ignored
assert.equal(checkApiVersion("0.1.0", "0.2.0").level, "refuse"); // pre-1.0 the minor IS the breaking slot
assert.equal(checkApiVersion("0.2.0", "0.1.0").level, "refuse");
for (const bad of ["1", "1.2", "v1.2.3", "01.2.3", "1.2.x", "", 1, undefined, null]) { for (const bad of ["1", "1.2", "v1.2.3", "01.2.3", "1.2.x", "", 1, undefined, null]) {
assert.equal(checkApiVersion(bad).level, "refuse", `${String(bad)} must refuse`); assert.equal(checkApiVersion(bad).level, "refuse", `${String(bad)} must refuse`);
} }
+6
View File
@@ -156,6 +156,12 @@ export function checkApiVersion(pluginVersion: unknown, hostVersion: string = HO
if (plugin.major !== host.major) { if (plugin.major !== host.major) {
return { level: "refuse", message: `plugin targets apiVersion ${pluginVersion}; host is ${hostVersion} — incompatible major` }; return { level: "refuse", message: `plugin targets apiVersion ${pluginVersion}; host is ${hostVersion} — incompatible major` };
} }
// Pre-1.0 the major is pinned at 0 until the 1.0.0 milestone, so a breaking change can only land
// as a minor (release-tooling/next-version.ts shifts every level down). Treating that as additive
// would let a stale plugin boot and fail at runtime instead of at discovery.
if (host.major === 0 && plugin.minor !== host.minor) {
return { level: "refuse", message: `plugin targets apiVersion ${pluginVersion}; host is ${hostVersion} — pre-1.0 a minor is a contract break, rebuild against ${hostVersion}` };
}
if (plugin.minor > host.minor) { if (plugin.minor > host.minor) {
return { level: "refuse", message: `plugin targets apiVersion ${pluginVersion} but host is ${hostVersion}; upgrade the host` }; return { level: "refuse", message: `plugin targets apiVersion ${pluginVersion} but host is ${hostVersion}; upgrade the host` };
} }