From 6437d7750c128702894d8909fdd88b401758db51 Mon Sep 17 00:00:00 2001 From: eunwoo song Date: Mon, 7 Sep 2026 01:57:51 +0900 Subject: [PATCH] fix(ci): reject missing internal version pins --- .github/workflows/ci.yml | 3 + docs/release.md | 4 ++ scripts/check_release_versions.sh | 8 ++- scripts/ci-local.sh | 1 + tests/release/release-version-pins.test.sh | 66 ++++++++++++++++++++++ 5 files changed, 80 insertions(+), 2 deletions(-) create mode 100755 tests/release/release-version-pins.test.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c531effc..1ee025ac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,6 +47,9 @@ jobs: - name: Check release version consistency run: bash scripts/check_release_versions.sh + - name: Test internal dependency pin coverage + run: bash tests/release/release-version-pins.test.sh + - name: Check public support and approval claims run: bash tests/release/public-claims.test.sh diff --git a/docs/release.md b/docs/release.md index f2dd4c5a..95ac8a30 100644 --- a/docs/release.md +++ b/docs/release.md @@ -136,6 +136,10 @@ git tag -s v0.2.5 -m "SysKnife v0.2.5" git push origin v0.2.5 ``` +The version check requires every internal path dependency to carry an inline +`version` matching the workspace release. Removing that field is an error even +when all remaining visible pins match. + The tag pattern does not accept prerelease suffixes. Do not move or reuse a published tag. If publication partly fails, diagnose and rerun the workflow on the same commit; do not publish a different tree under the same version. diff --git a/scripts/check_release_versions.sh b/scripts/check_release_versions.sh index 67544e1f..a3b0e318 100755 --- a/scripts/check_release_versions.sh +++ b/scripts/check_release_versions.sh @@ -62,8 +62,7 @@ fi pins="$(grep -rn '^sysknife-[a-z-]* = {' \ "$repo_root"/crates/*/Cargo.toml \ "$repo_root"/apps/sysknife-cli/Cargo.toml \ - "$repo_root"/apps/sysknife-shell/src-tauri/Cargo.toml | - grep 'version = ' || true)" + "$repo_root"/apps/sysknife-shell/src-tauri/Cargo.toml || true)" # An empty result means the manifests moved, not that every pin agrees. Fail # loudly rather than reporting success for a check that inspected nothing. @@ -77,6 +76,11 @@ pin_count=0 while IFS= read -r pin; do pin_count=$((pin_count + 1)) pinned="$(printf '%s' "$pin" | sed -n 's/.*version = "\([^"]*\)".*/\1/p')" + if [[ -z "$pinned" ]]; then + printf 'Internal dependency is missing an explicit version pin:\n %s\n' \ + "$pin" >&2 + exit 1 + fi if [[ "$pinned" != "$baseline" ]]; then printf 'Internal dependency pin does not match package version %s:\n %s\n' \ "$baseline" "$pin" >&2 diff --git a/scripts/ci-local.sh b/scripts/ci-local.sh index 40771ee0..0b8cdfad 100755 --- a/scripts/ci-local.sh +++ b/scripts/ci-local.sh @@ -262,6 +262,7 @@ run_hygiene_group() { printf '\n### hygiene\n' run_step 'hygiene: check_repo_completeness.sh' bash "$repo_root/scripts/check_repo_completeness.sh" run_step 'hygiene: check_release_versions.sh' bash "$repo_root/scripts/check_release_versions.sh" + run_step 'hygiene: release-version-pins.test.sh' bash "$repo_root/tests/release/release-version-pins.test.sh" run_step 'hygiene: public-claims.test.sh' bash "$repo_root/tests/release/public-claims.test.sh" run_step 'hygiene: npm test --prefix packages/setup' npm test --prefix "$repo_root/packages/setup" run_step 'hygiene: registry-manifest.test.sh' bash "$repo_root/tests/release/registry-manifest.test.sh" diff --git a/tests/release/release-version-pins.test.sh b/tests/release/release-version-pins.test.sh new file mode 100755 index 00000000..08900f58 --- /dev/null +++ b/tests/release/release-version-pins.test.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +fixture="$(mktemp -d)" +trap 'rm -rf "$fixture"' EXIT + +paths=( + scripts/check_release_versions.sh + apps/sysknife-cli/Cargo.toml + apps/sysknife-shell/package.json + apps/sysknife-shell/package-lock.json + apps/sysknife-shell/src-tauri/Cargo.toml + apps/sysknife-shell/src-tauri/tauri.conf.json + crates/sysknife-brain/Cargo.toml + crates/sysknife-core/Cargo.toml + crates/sysknife-daemon-test/Cargo.toml + crates/sysknife-daemon/Cargo.toml + crates/sysknife-proto/Cargo.toml + crates/sysknife-types/Cargo.toml + packages/setup/package.json + .codex-plugin/plugin.json + server.json +) + +for path in "${paths[@]}"; do + mkdir -p "$fixture/$(dirname "$path")" + cp "$repo_root/$path" "$fixture/$path" +done + +bash "$fixture/scripts/check_release_versions.sh" >/dev/null + +python3 - "$fixture/crates/sysknife-brain/Cargo.toml" <<'PY' +from pathlib import Path +import re +import sys + +manifest = Path(sys.argv[1]) +contents = manifest.read_text() +mutated, replacements = re.subn( + r'^(sysknife-core = \{ path = "[^"]*"), version = "[^"]*"( \})$', + r'\1\2', + contents, + count=1, + flags=re.MULTILINE, +) +if replacements != 1: + raise SystemExit("fixture could not remove the sysknife-core version pin") +manifest.write_text(mutated) +PY + +if output="$(bash "$fixture/scripts/check_release_versions.sh" 2>&1)"; then + printf 'release-version-pins: missing inline version unexpectedly passed\n' >&2 + exit 1 +fi + +grep -Fq 'crates/sysknife-brain/Cargo.toml' <<<"$output" || { + printf 'release-version-pins: failure omitted the manifest path: %s\n' "$output" >&2 + exit 1 +} +grep -Fq 'missing an explicit version pin' <<<"$output" || { + printf 'release-version-pins: failure did not explain the missing pin: %s\n' "$output" >&2 + exit 1 +} + +printf 'release-version-pins: missing inline versions fail explicitly.\n'