diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 405f8a0..41424c8 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -30,7 +30,7 @@ A reusable workflow cannot reliably check out *its own* repo's scripts: in a `wo `scripts/*.sh` is the source of truth; the inline copy is a derived artifact. **Editing a script means updating its inline copy too**, or `check-inline-sync.sh` fails CI. The sync is byte-for-byte after known normalizations (10-space YAML indent strip, shebang strip, function-wrapper strip). The pairs are listed in `check-inline-sync.sh` (`INLINE_PAIRS`): -- `dependency-safety.yml` embeds `extract-deps.sh`, `check-release-age.sh`, `diff-touches-lockfile.sh`, `pr-body-to-deps.sh`, `classify-touched-paths.sh`, `pyproject-bump-extract.sh`, and `safety-verdict.sh` +- `dep-safety.yml` embeds `extract-deps.sh`, `check-release-age.sh`, `diff-touches-lockfile.sh`, `pr-body-to-deps.sh`, `classify-touched-paths.sh`, `pyproject-bump-extract.sh`, and `safety-verdict.sh` - `tag-release.yml` embeds `bump-version-files.sh` `lint-workflow-call.sh` is the partner guard: it fails CI if any `workflow_call` file reintroduces a caller-scoped ref as a checkout `ref:`. @@ -39,13 +39,13 @@ A reusable workflow cannot reliably check out *its own* repo's scripts: in a `wo **Consumer-facing reusable workflows:** -- `dependency-safety.yml` — verifies the native-Dependabot-cooldown invariant on each Dependabot PR. Pipeline: extract → fallback → guard → age check → GHSA/OSV scan → scorecard → comment → labels; the verdict layer is deterministic: `failure` on age violation (when `fail_on_age_violation: true`), `error` on extraction/scan failure, `success` otherwise. Verdict translation lives in `safety-verdict.sh`. No rescan companion — verifier is single-shot per PR event. +- `dep-safety.yml` — verifies the native-Dependabot-cooldown invariant on each Dependabot PR. Pipeline: extract → fallback → guard → age check → GHSA/OSV scan → scorecard → comment → labels; the verdict layer is deterministic: `failure` on age violation (when `fail_on_age_violation: true`), `error` on extraction/scan failure, `success` otherwise. Verdict translation lives in `safety-verdict.sh`. No rescan companion — verifier is single-shot per PR event. (Renamed from `dependency-safety.yml` on `main` to force a fresh workflow registry entry; public `@v3` consumers still pin to the historical filename until a new release is cut.) - `tag-release.yml` — computes the next semver tag from Conventional Commits, optionally runs `bump-version-files.sh` against `.version-bump.json`, creates the tag via the GitHub Git Data API (so commits/tags auto-sign under the App identity). Requires a GitHub App key (`RELEASE_BOT_PRIVATE_KEY` secret, `RELEASE_BOT_APP_ID` var). - `publish-pypi.yml` — `uv build` → TestPyPI (with install verification) → PyPI via OIDC trusted publishing → GitHub Release. **Release machinery for this repo itself:** `release-self.yml` (manual `workflow_dispatch`) → calls `tag-release.yml` → calls `release.yml`. `release.yml` is `workflow_call`-only (no `push: tags` trigger) and floats the major/minor tags (`v2` → `v2.3` → `v2.3.1`). Merging to `main` does **not** auto-release; a release is always a deliberate `release-self.yml` dispatch. -**CI:** `ci-scripts.yml` (bats + inline-sync + workflow-call lint), `ci-safety.yml` (dogfoods `dependency-safety.yml` on this repo's own Dependabot PRs), `security.yml` (zizmor workflow analysis). +**CI:** `ci-scripts.yml` (bats + inline-sync + workflow-call lint), `ci-safety.yml` (dogfoods `dep-safety.yml` on this repo's own Dependabot PRs), `security.yml` (zizmor workflow analysis). ## Conventions diff --git a/.github/workflows/ci-safety.yml b/.github/workflows/ci-safety.yml index 53698d6..d5ceb52 100644 --- a/.github/workflows/ci-safety.yml +++ b/.github/workflows/ci-safety.yml @@ -17,6 +17,6 @@ concurrency: jobs: safety: - uses: ./.github/workflows/dependency-safety.yml + uses: ./.github/workflows/dep-safety.yml with: auto_merge: true diff --git a/.github/workflows/dependency-safety.yml b/.github/workflows/dep-safety.yml similarity index 100% rename from .github/workflows/dependency-safety.yml rename to .github/workflows/dep-safety.yml diff --git a/scripts/check-inline-sync.sh b/scripts/check-inline-sync.sh index c1dd5e9..570b919 100755 --- a/scripts/check-inline-sync.sh +++ b/scripts/check-inline-sync.sh @@ -11,14 +11,14 @@ set -euo pipefail # Each entry: ":" INLINE_PAIRS=( - ".github/workflows/dependency-safety.yml:scripts/extract-deps.sh" - ".github/workflows/dependency-safety.yml:scripts/check-release-age.sh" - ".github/workflows/dependency-safety.yml:scripts/diff-touches-lockfile.sh" - ".github/workflows/dependency-safety.yml:scripts/pr-body-to-deps.sh" - ".github/workflows/dependency-safety.yml:scripts/safety-verdict.sh" - ".github/workflows/dependency-safety.yml:scripts/classify-touched-paths.sh" + ".github/workflows/dep-safety.yml:scripts/extract-deps.sh" + ".github/workflows/dep-safety.yml:scripts/check-release-age.sh" + ".github/workflows/dep-safety.yml:scripts/diff-touches-lockfile.sh" + ".github/workflows/dep-safety.yml:scripts/pr-body-to-deps.sh" + ".github/workflows/dep-safety.yml:scripts/safety-verdict.sh" + ".github/workflows/dep-safety.yml:scripts/classify-touched-paths.sh" ".github/workflows/tag-release.yml:scripts/bump-version-files.sh" - ".github/workflows/dependency-safety.yml:scripts/pyproject-bump-extract.sh" + ".github/workflows/dep-safety.yml:scripts/pyproject-bump-extract.sh" ) YAML_INDENT=" " # exactly 10 spaces — matches the `run: |` indent diff --git a/tests/guard-runtime.bats b/tests/guard-runtime.bats index fbef4b5..bbe0d3b 100644 --- a/tests/guard-runtime.bats +++ b/tests/guard-runtime.bats @@ -22,7 +22,7 @@ extract_guard_block() { } @test "safety: issue #62 case (DEPS_TSV non-empty + UNSUPPORTED non-empty) — guard fires" { - block=$(extract_guard_block .github/workflows/dependency-safety.yml) + block=$(extract_guard_block .github/workflows/dep-safety.yml) DEPS_TSV=$'mypy\t1.20.1\tpypi' TOUCHED_PATHS=$'package-lock.json\nuv.lock' EFFECTIVE_TOUCHED="$TOUCHED_PATHS" @@ -33,7 +33,7 @@ extract_guard_block() { } @test "safety: issue #52 case (DEPS_TSV empty + TOUCHED non-empty + UNSUPPORTED empty) — guard fires" { - block=$(extract_guard_block .github/workflows/dependency-safety.yml) + block=$(extract_guard_block .github/workflows/dep-safety.yml) DEPS_TSV="" TOUCHED_PATHS="uv.lock" EFFECTIVE_TOUCHED="$TOUCHED_PATHS" @@ -44,7 +44,7 @@ extract_guard_block() { } @test "safety: clean supported case (DEPS_TSV non-empty + UNSUPPORTED empty) — guard does NOT fire" { - block=$(extract_guard_block .github/workflows/dependency-safety.yml) + block=$(extract_guard_block .github/workflows/dep-safety.yml) DEPS_TSV=$'requests\t2.32.0\tpypi' TOUCHED_PATHS="requirements.txt" EFFECTIVE_TOUCHED="$TOUCHED_PATHS" @@ -54,7 +54,7 @@ extract_guard_block() { } @test "safety: no touched paths (empty diff) — guard does NOT fire" { - block=$(extract_guard_block .github/workflows/dependency-safety.yml) + block=$(extract_guard_block .github/workflows/dep-safety.yml) DEPS_TSV="" TOUCHED_PATHS="" EFFECTIVE_TOUCHED="" diff --git a/tests/guard-shape.bats b/tests/guard-shape.bats index 166d2e2..d5d85f4 100644 --- a/tests/guard-shape.bats +++ b/tests/guard-shape.bats @@ -7,11 +7,11 @@ # unit-testable; this guard prevents accidental ordering regressions. WORKFLOWS=( - ".github/workflows/dependency-safety.yml" + ".github/workflows/dep-safety.yml" ) -@test "dependency-safety.yml: TOUCHED_PATHS/UNSUPPORTED_PATHS hoisted above Layer 2" { - yaml=".github/workflows/dependency-safety.yml" +@test "dep-safety.yml: TOUCHED_PATHS/UNSUPPORTED_PATHS hoisted above Layer 2" { + yaml=".github/workflows/dep-safety.yml" hoist_line=$(grep -n "UNSUPPORTED_PATHS=\$(printf" "$yaml" | head -1 | cut -d: -f1) layer2_line=$(grep -n "Layer 2: PR-body fallback" "$yaml" | head -1 | cut -d: -f1) [ -n "$hoist_line" ] @@ -19,8 +19,8 @@ WORKFLOWS=( [ "$hoist_line" -lt "$layer2_line" ] } -@test "dependency-safety.yml: Layer 3 guard checks UNSUPPORTED_PATHS before zero-rows elif" { - yaml=".github/workflows/dependency-safety.yml" +@test "dep-safety.yml: Layer 3 guard checks UNSUPPORTED_PATHS before zero-rows elif" { + yaml=".github/workflows/dep-safety.yml" unsupported_line=$(grep -n 'if \[ -n "\$UNSUPPORTED_PATHS" \]; then' "$yaml" | head -1 | cut -d: -f1) elif_line=$(grep -n 'elif \[ -z "\$(echo "\$DEPS_TSV" | sed' "$yaml" | head -1 | cut -d: -f1) [ -n "$unsupported_line" ]