diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ce0142..d255d41 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,10 +9,10 @@ on: jobs: check: name: Lint and validate - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Lint run: make lint @@ -22,13 +22,13 @@ jobs: link-check: name: Check links - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Check links - uses: lycheeverse/lychee-action@v2 + uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2.9.0 with: args: >- --exclude-loopback diff --git a/Makefile b/Makefile index 9a0cfd2..7c52f6a 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,9 @@ lint: validate: bash scripts/validate-workspace.sh +validate-workflows: + bash scripts/validate-workflow-policy.sh . + lint-fix: npx rumdl check --fix . @@ -15,4 +18,4 @@ check-links: ci: lint validate check-links -.PHONY: lint validate lint-fix format check-links ci +.PHONY: lint validate validate-workflows lint-fix format check-links ci diff --git a/scripts/setup-github-repo.sh b/scripts/setup-github-repo.sh index 67d2783..dadb879 100755 --- a/scripts/setup-github-repo.sh +++ b/scripts/setup-github-repo.sh @@ -153,5 +153,5 @@ echo " Org-level labels: bug, enhancement, question, duplicate, needs-repro, ne echo " stale, good first issue, wontfix, invalid, security, needs-human-review" echo "" echo " Next required step" -echo " Run sdk-sync before opening implementation PRs so .github/workflows/semantic-pr.yml" +echo " Run sdk-sync before opening implementation PRs so .github/workflows/semantic-pull-request.yml" echo " and other managed community files are present." diff --git a/scripts/validate-workflow-policy.sh b/scripts/validate-workflow-policy.sh new file mode 100644 index 0000000..bd6bd15 --- /dev/null +++ b/scripts/validate-workflow-policy.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# Validates Altertable's GitHub Actions supply-chain and runtime policy. +# +# Usage: ./scripts/validate-workflow-policy.sh [repository-root] + +set -euo pipefail + +REPOSITORY_ROOT="${1:-.}" +REQUIRED_UBUNTU_RUNNER="ubuntu-24.04" +WORKFLOWS_DIRECTORY="$REPOSITORY_ROOT/.github/workflows" + +fail() { + echo "ERROR: $*" >&2 + exit 1 +} + +[[ -d "$WORKFLOWS_DIRECTORY" ]] || exit 0 + +while IFS= read -r -d '' workflow; do + while IFS= read -r runner; do + [[ "$runner" == "$REQUIRED_UBUNTU_RUNNER" ]] || fail \ + "$workflow must use $REQUIRED_UBUNTU_RUNNER instead of $runner" + done < <(sed -nE 's/^[[:space:]]*runs-on:[[:space:]]*(ubuntu-(latest|[0-9]+\.[0-9]+))[[:space:]]*(#.*)?$/\1/p' "$workflow") + + while IFS= read -r action_reference; do + action_reference="${action_reference%%[[:space:]]#*}" + case "$action_reference" in + ./*) continue ;; + docker://*) fail "$workflow must pin container actions by digest: $action_reference" ;; + esac + + [[ "$action_reference" =~ ^[^@]+@[0-9a-f]{40}$ ]] || fail \ + "$workflow has an action that is not pinned to a commit SHA: $action_reference" + done < <(sed -nE \ + -e 's/^[[:space:]]*-[[:space:]]*uses:[[:space:]]*([^[:space:]#]+).*/\1/p' \ + -e 's/^[[:space:]]*uses:[[:space:]]*([^[:space:]#]+).*/\1/p' \ + "$workflow") +done < <(find "$WORKFLOWS_DIRECTORY" -type f \( -name '*.yml' -o -name '*.yaml' \) -print0) + +if grep -R -qE 'uses:[[:space:]]*actions/setup-node@' "$WORKFLOWS_DIRECTORY"; then + [[ -f "$REPOSITORY_ROOT/.node-version" ]] || fail \ + "$REPOSITORY_ROOT uses actions/setup-node but has no .node-version" + grep -R -qE 'node-version-file:[[:space:]]*\.node-version([[:space:]]|$)' "$WORKFLOWS_DIRECTORY" || fail \ + "$REPOSITORY_ROOT must configure actions/setup-node with node-version-file: .node-version" + if grep -R -qE '^[[:space:]]*node-version:[[:space:]]*[^[:space:]#]' "$WORKFLOWS_DIRECTORY"; then + fail "$REPOSITORY_ROOT must not hard-code node-version in workflows; use .node-version" + fi +fi + +if grep -R -qE 'uses:[[:space:]]*oven-sh/setup-bun@' "$WORKFLOWS_DIRECTORY"; then + [[ -f "$REPOSITORY_ROOT/.bun-version" ]] || fail \ + "$REPOSITORY_ROOT uses oven-sh/setup-bun but has no .bun-version" + grep -R -qE 'bun-version-file:[[:space:]]*\.bun-version([[:space:]]|$)' "$WORKFLOWS_DIRECTORY" || fail \ + "$REPOSITORY_ROOT must configure oven-sh/setup-bun with bun-version-file: .bun-version" + if grep -R -qE '^[[:space:]]*bun-version:[[:space:]]*[^[:space:]#]' "$WORKFLOWS_DIRECTORY"; then + fail "$REPOSITORY_ROOT must not hard-code bun-version in workflows; use .bun-version" + fi +fi + +echo "Workflow policy passed: $REPOSITORY_ROOT" diff --git a/scripts/validate-workspace.sh b/scripts/validate-workspace.sh index 7d1f6d3..56234eb 100755 --- a/scripts/validate-workspace.sh +++ b/scripts/validate-workspace.sh @@ -26,7 +26,7 @@ require_file scripts/spec-status.sh require_file scripts/ecosystem-status.sh require_file scripts/subscribe-repos.sh require_file schemas/heartbeat-state.example.json -require_file skills/sdk-sync/templates/.github/workflows/semantic-pr.yml +require_file skills/sdk-sync/templates/.github/workflows/semantic-pull-request.yml jq empty repositories.config.json @@ -81,8 +81,12 @@ if grep -R -nE "Close the issue|Close the PR|close the issue|close the PR|will b fail "autonomous issue/PR close instruction found" fi -if ! grep -q "action-semantic-pull-request@v5" skills/sdk-sync/templates/.github/workflows/semantic-pr.yml; then - fail "semantic PR title template does not use the required action" -fi +bash scripts/validate-workflow-policy.sh . + +template_root="$(mktemp -d)" +trap 'rm -rf "$template_root"' EXIT +mkdir -p "$template_root/.github" +cp -R skills/sdk-sync/templates/.github/workflows "$template_root/.github/workflows" +bash scripts/validate-workflow-policy.sh "$template_root" echo "Workspace validation passed" diff --git a/skills/sdk-release/SKILL.md b/skills/sdk-release/SKILL.md index 822e582..cfc1787 100644 --- a/skills/sdk-release/SKILL.md +++ b/skills/sdk-release/SKILL.md @@ -67,7 +67,7 @@ Use **release-please** GitHub Action for: 3. GitHub Release creation with release notes. 4. Triggering registry publish on release. -Because Altertable squash-merges pull requests and release-please derives release notes and version bumps from the merged commit subject, every repository using release-please must validate pull request titles in CI. The required gate is a dedicated GitHub Actions workflow using `amannn/action-semantic-pull-request@v5`, triggered on `pull_request_target` for `opened`, `edited`, and `synchronize`. Allowed title types are: +Because Altertable squash-merges pull requests and release-please derives release notes and version bumps from the merged commit subject, every repository using release-please must validate pull request titles in CI. The required gate is a dedicated GitHub Actions workflow using `amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50` (v6.1.1), triggered on `pull_request_target` for `opened`, `edited`, and `synchronize`. Allowed title types are: - `feat` - `fix` @@ -81,7 +81,7 @@ Because Altertable squash-merges pull requests and release-please derives releas - `build` - `ci` -Name the workflow clearly (for example `Semantic Pull Request`), keep it separate from language-specific CI when possible, and treat missing PR-title validation as release automation drift that must be patched across affected repositories. The canonical workflow is managed by [sdk-sync](../sdk-sync/SKILL.md) at `skills/sdk-sync/templates/.github/workflows/semantic-pr.yml`. +Name the workflow clearly (for example `Semantic Pull Request`), keep it separate from language-specific CI when possible, and treat missing PR-title validation as release automation drift that must be patched across affected repositories. The canonical workflow is managed by [sdk-sync](../sdk-sync/SKILL.md) at `skills/sdk-sync/templates/.github/workflows/semantic-pull-request.yml`. It and every repository workflow must follow sdk-sync's GitHub Actions policy: Ubuntu 24.04 LTS, immutable action SHAs, and `.node-version`/`.bun-version` runtime files where those toolchains are used. Commit message prefixes: diff --git a/skills/sdk-sync/SKILL.md b/skills/sdk-sync/SKILL.md index c1f928b..4ea1a14 100644 --- a/skills/sdk-sync/SKILL.md +++ b/skills/sdk-sync/SKILL.md @@ -15,6 +15,17 @@ All files in the [`templates/`](./templates/) folder are the source of truth and Managed files include community docs, issue/PR templates, funding metadata, and release automation guardrails. Language-specific CI workflows are owned by each SDK repo, but the semantic PR title workflow is shared because release-please depends on squash-merge titles. +## GitHub Actions policy + +Apply this policy to every workflow in each repository, including language-specific CI, release, security, and semantic-title workflows: + +- Linux jobs use `ubuntu-24.04`, the current GitHub-hosted Ubuntu LTS image; do not use `ubuntu-latest` or an older Ubuntu image. macOS and Windows jobs retain their platform-specific pinned image where required. +- Every third-party `uses:` reference is pinned to a 40-character commit SHA with its reviewed release tag in a comment. Local reusable workflows remain relative references. +- A repository that uses `actions/setup-node` commits a root `.node-version`; every Node setup reads it through `node-version-file: .node-version` rather than hard-coding a workflow value. +- A repository that uses `oven-sh/setup-bun` commits a root `.bun-version`; every Bun setup reads it through `bun-version-file: .bun-version` rather than hard-coding a workflow value. + +`.node-version` and `.bun-version` are repository-owned runtime contracts, not shared templates: preserve the version that the repository supports and make its workflows consume that file. During an audit, run `bash scripts/validate-workflow-policy.sh ` after fetching the target repository. Resolve any intentionally dynamic matrix runner or runtime version to an explicit, pinned value before opening the sync PR. + ### Template variables Templated files contain `{variable}` placeholders. Render them with repo-specific values during sync: @@ -41,8 +52,9 @@ Templated files contain `{variable}` placeholders. Render them with repo-specifi 1. Read `repositories.config.json` and iterate over the `sdks` array (do not include the `workspace` entry). 2. Clone or fetch all SDK repos from the `sdks` array. 3. For each managed file, compare the repo's version against the source of truth. -4. Treat a missing `.github/workflows/semantic-pr.yml` as release automation drift for every repo that uses release-please. -5. Report drift: +4. Audit every `.github/workflows/*.{yml,yaml}` against the GitHub Actions policy and record runner, action-pin, and runtime-version-file drift. +5. Treat a missing `.github/workflows/semantic-pull-request.yml` as release automation drift for every repo that uses release-please. +6. Report drift: ```text DRIFT REPORT @@ -50,7 +62,7 @@ DRIFT REPORT altertable-lakehouse-ruby: ✗ SECURITY.md — missing ✗ CONTRIBUTING.md — outdated (missing Conventional Commits section) - ✗ .github/workflows/semantic-pr.yml — missing + ✗ .github/workflows/semantic-pull-request.yml — missing ✓ LICENSE — ok altertable-py: @@ -65,6 +77,7 @@ For each repo with drift: 1. Copy verbatim files from `templates/` directly. 2. Render templated files from `templates/` with repo-specific variables (see **Template variables** above). +3. For workflow-policy drift, update only the affected repository-owned workflow(s) and root runtime version file(s); do not overwrite language-specific workflow logic with a shared template. ### Phase 3: Open PRs diff --git a/skills/sdk-sync/templates/.github/workflows/semantic-pr.yml b/skills/sdk-sync/templates/.github/workflows/semantic-pull-request.yml similarity index 61% rename from skills/sdk-sync/templates/.github/workflows/semantic-pr.yml rename to skills/sdk-sync/templates/.github/workflows/semantic-pull-request.yml index 28320d2..970e6d8 100644 --- a/skills/sdk-sync/templates/.github/workflows/semantic-pr.yml +++ b/skills/sdk-sync/templates/.github/workflows/semantic-pull-request.yml @@ -10,13 +10,18 @@ on: permissions: pull-requests: read +concurrency: + group: semantic-pr-${{ github.event.pull_request.number }} + cancel-in-progress: true + jobs: - semantic-pr: + main: name: Validate PR title - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 + timeout-minutes: 5 steps: - - name: Validate title - uses: amannn/action-semantic-pull-request@v5 + - name: Validate pull request title + uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: