diff --git a/.github/workflows/server-image.yml b/.github/workflows/server-image.yml index b02b69b2..fcef121c 100644 --- a/.github/workflows/server-image.yml +++ b/.github/workflows/server-image.yml @@ -114,8 +114,7 @@ jobs: publish: if: >- github.repository == 'ajisaacs/OpenNest' && - ((github.event_name == 'release' && github.event.release.prerelease == false) || - (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/master')) + github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/master' runs-on: ubuntu-latest timeout-minutes: 40 permissions: diff --git a/docs/releasing.md b/docs/releasing.md index 25f43418..190a18ba 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -72,16 +72,13 @@ or attestations (`--provenance=false --sbom=false`), and runs the real-client container persistence/backup/restore smoke. A Windows `v*` tag build alone never publishes a server image. -Publication requires owner-approved release intent: a **published, non-prerelease GitHub Release** -or an explicit `Server image` dispatch **from `master`**, naming an existing strict -`vX.Y.Z` tag (no prerelease suffix, leading zero, or extra suffix). GitHub pre-releases -publish only the Windows assets; they skip the server image publish and verification -jobs. Changing a pre-release to a full release later is a separate publication -decision. The tag must resolve to -a full commit already on `master`. For a published Release, that peeled commit -must also equal the event's full `GITHUB_SHA`; a retargeted tag is refused. Manual -dispatch intentionally resolves the approved existing tag, not the workflow's -`master` SHA. The job checks out that exact source, reruns all +Publication requires an explicit, owner-approved `Server image` dispatch **from +`master`**, naming an existing strict `vX.Y.Z` tag (no prerelease suffix, leading +zero, or extra suffix). Publishing a GitHub Release, stable or pre-release, only +validates the image; it never publishes it, so a desktop release and a server image +release are separate decisions. The tag must resolve to a full commit already on +`master`. The dispatch intentionally resolves the approved existing tag, not the +workflow's `master` SHA. The job checks out that exact source, reruns all server/image gates, and pushes the *same smoked image* to `ghcr.io/ajisaacs/opennest-server:X.Y.Z` and `:sha-` only. Both tags must be absent; retries after even a partial upload stop instead of overwriting. There diff --git a/scripts/test_server_image_release.py b/scripts/test_server_image_release.py index 72e18c02..30536cf7 100644 --- a/scripts/test_server_image_release.py +++ b/scripts/test_server_image_release.py @@ -698,11 +698,12 @@ class ImmutableTagCollisions(unittest.TestCase): class WorkflowBehavior(unittest.TestCase): - def test_prerelease_skips_ghcr_publish_but_stable_release_and_manual_dispatch_remain(self): + def test_only_a_manual_dispatch_from_master_publishes_to_ghcr(self): workflow = pathlib.Path(__file__).resolve().parents[1] / ".github/workflows/server-image.yml" publish = workflow.read_text().split("\n publish:\n", 1)[1].split("\n verify-published:\n", 1)[0] condition = publish.split(" if: >-\n", 1)[1].split(" runs-on:", 1)[0] - self.assertIn("github.event_name == 'release' && github.event.release.prerelease == false", condition) + # Publishing a GitHub Release, stable or pre-release, never publishes the image. + self.assertNotIn("'release'", condition) self.assertIn("github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/master'", condition) def test_build_log_pipeline_preserves_docker_failure(self):