From e30ef82716a9343c4637808d9fe6b1b3dbb7f001 Mon Sep 17 00:00:00 2001 From: Yudhi Armyndharis Date: Tue, 25 Aug 2026 12:07:31 +0700 Subject: [PATCH] ci: run the catalog liveness check nightly instead of on every push A release lands as merge-then-tag, so between those two moments the catalog genuinely points at releases that do not exist yet. Running the check on push therefore made `main` red by construction on every release: the 0.23.3 merge went red thirteen seconds after landing, for a condition that resolved as soon as the tags were pushed. A gate that is routinely red for a known-benign reason is one people learn to ignore, which costs more than the window it covers. Nightly still catches the failure it exists for, a set of tags that is never pushed at all, and workflow_dispatch covers the case where the answer is wanted immediately: run it by hand right after pushing release tags. Moves the job into its own workflow, matching download-badges.yml, which lets ci.yml go back to a single job with no event guards. --- .github/workflows/catalog-live.yml | 35 ++++++++++++++++++++++++++++++ .github/workflows/ci.yml | 28 ------------------------ scripts/catalog-live-check.mjs | 7 +++++- 3 files changed, 41 insertions(+), 29 deletions(-) create mode 100644 .github/workflows/catalog-live.yml diff --git a/.github/workflows/catalog-live.yml b/.github/workflows/catalog-live.yml new file mode 100644 index 0000000..1628ae3 --- /dev/null +++ b/.github/workflows/catalog-live.yml @@ -0,0 +1,35 @@ +name: catalog-live + +# Proves the PUBLISHED catalog is installable: every `download` URL in plugins.json resolves and its +# bytes match the pinned `#sha256=`. The `catalog:check` gate in ci.yml cannot see this, it only proves +# plugins.json is regenerable from the working tree, so a version bump merged without its tags stays +# green there while every dashboard install answers 404. +# +# Deliberately NOT run on push or pull_request. A release lands as merge-then-tag, so between those two +# moments the catalog genuinely does point at releases that do not exist yet, and a push-triggered run +# is red by construction on every release. A gate that is routinely red for a known-benign reason is a +# gate people learn to ignore, which costs more than the window it covers. Nightly still catches tags +# that are never pushed at all, and workflow_dispatch covers the one case where the answer is wanted +# immediately: run it by hand right after pushing a set of release tags. +on: + schedule: + # The catalog can also rot with nobody pushing: a release asset deleted, a tag moved, a repository + # renamed. Once a day is enough for a condition measured in hours-to-notice, not minutes. + - cron: '17 5 * * *' + workflow_dispatch: + +jobs: + catalog-live: + runs-on: ubuntu-latest + # Reads public release assets over HTTPS and nothing else: no git writes, no API calls. + permissions: + contents: read + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 + with: + node-version: 22 + - name: Every catalog download resolves and matches its sha256 pin + run: node scripts/catalog-live-check.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c66a2e..3049f4b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,15 +5,9 @@ on: branches: [main] pull_request: branches: [main] - schedule: - # The catalog can rot without anyone pushing: a release asset deleted or a tag moved breaks installs - # while every commit stays green. Check it once a day. - - cron: '17 5 * * *' - workflow_dispatch: jobs: build: - if: github.event_name == 'push' || github.event_name == 'pull_request' runs-on: ubuntu-latest # Nothing in this job reaches the repository through git or the API: it installs, type-checks, # tests, packages and loads bundles. Without these two lines it inherits the repository default @@ -48,25 +42,3 @@ jobs: - name: Every built bundle loads as a plugin run: node scripts/loader-check.mjs - - # Proves the published catalog is actually installable: every `download` URL resolves and its bytes - # match the pinned sha256. `catalog:check` in the job above cannot see this, it only proves - # plugins.json is regenerable from the tree, so a version bump merged without its tags stays green - # there while every dashboard install 404s. - # - # Deliberately NOT run on pull_request: the PR that bumps versions is red until its tags are pushed, - # which is the correct order. This gate is what catches the tags never being pushed at all. - catalog-live: - if: github.event_name != 'pull_request' - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - persist-credentials: false - - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 - with: - node-version: 22 - - name: Every catalog download resolves and matches its sha256 pin - run: node scripts/catalog-live-check.mjs diff --git a/scripts/catalog-live-check.mjs b/scripts/catalog-live-check.mjs index cc12f20..66981e9 100644 --- a/scripts/catalog-live-check.mjs +++ b/scripts/catalog-live-check.mjs @@ -13,7 +13,12 @@ // new bytes while the URL still names the old tag, and installs fail on a sha256 mismatch. // // Neither is visible to any other check in this repo, and both are invisible until a user tries to -// install. Run this on push to `main` and nightly. No token: these are public release assets. +// install. No token: these are public release assets. +// +// Wired to run nightly and on demand (.github/workflows/catalog-live.yml), NOT on push. A release +// lands as merge-then-tag, so a push-triggered run is red by construction in the window between the +// two, and a gate that is routinely red for a benign reason gets ignored. Run it by hand right after +// pushing a set of release tags, which is exactly when the answer is wanted. // // Usage: node scripts/catalog-live-check.mjs [path/to/plugins.json] // Exit 0 when every entry resolves and matches; exit 1 listing each entry that does not.