Verify Spec Kit Release #201
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
| name: Verify Spec Kit Release | |
| on: | |
| workflow_run: | |
| workflows: | |
| - Publish Agent Skills | |
| types: | |
| - completed | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: verify-spec-kit-release | |
| cancel-in-progress: false | |
| jobs: | |
| verify: | |
| name: Verify released Spec Kit extension | |
| if: >- | |
| (github.event_name == 'workflow_run' && | |
| github.event.workflow_run.conclusion == 'success' && | |
| github.event.workflow_run.event == 'push') || | |
| (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main') | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout trusted main | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| ref: main | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Resolve and verify source commit | |
| id: source | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| SOURCE_RUN_ID: ${{ github.event.workflow_run.id }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [[ "${GITHUB_EVENT_NAME}" == "workflow_run" ]]; then | |
| if [[ ! "$SOURCE_RUN_ID" =~ ^[0-9]+$ ]]; then | |
| echo "Unable to resolve the completed publisher run." >&2 | |
| exit 1 | |
| fi | |
| run_path="repos/${GITHUB_REPOSITORY}/actions/runs/${SOURCE_RUN_ID}" | |
| source_event="$(gh api "$run_path" --jq '.event')" | |
| source_branch="$(gh api "$run_path" --jq '.head_branch')" | |
| sha="$(gh api "$run_path" --jq '.head_sha')" | |
| if [[ "$source_event" != "push" || "$source_branch" != "main" ]]; then | |
| echo "Refusing publisher run ${SOURCE_RUN_ID}: expected a push on main." >&2 | |
| exit 1 | |
| fi | |
| else | |
| sha="${GITHUB_SHA}" | |
| fi | |
| if [[ ! "$sha" =~ ^[0-9a-f]{40}$ ]]; then | |
| echo "Unable to resolve a valid main commit to verify." >&2 | |
| exit 1 | |
| fi | |
| # actions/checkout only checks out fixed trusted main. Before any | |
| # repository code runs, prove the completed publisher SHA belongs to | |
| # that main history, then detach to the exact release source. | |
| git fetch --force origin main | |
| if ! git merge-base --is-ancestor "$sha" origin/main; then | |
| echo "Refusing to verify $sha because it is not in trusted main history." >&2 | |
| exit 1 | |
| fi | |
| git checkout --detach "$sha" | |
| echo "sha=$sha" >> "$GITHUB_OUTPUT" | |
| - name: Setup Node.js | |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: "24" | |
| cache: npm | |
| - name: Install repository dependencies | |
| run: npm ci | |
| - name: Validate Spec Kit package and shared version | |
| run: node tests/validate-spec-kit.mjs | |
| - name: Install pinned Spec Kit CLI | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| python3 -m pip install --user --disable-pip-version-check \ | |
| "git+https://github.com/github/spec-kit.git@ead30d9cfb99c07b3073afa73e7b40a64015f17f" | |
| echo "$HOME/.local/bin" >> "$GITHUB_PATH" | |
| - name: Verify shared release owns the exact Spec Kit package | |
| id: release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| VERSION="$(node -p "JSON.parse(require('fs').readFileSync('package.json', 'utf8')).version")" | |
| TAG="v${VERSION}" | |
| PACKAGE_PATHS=(extension.yml commands README.md LICENSE) | |
| if ! gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then | |
| echo "Expected shared Agent Skills release $TAG does not exist yet." >&2 | |
| exit 1 | |
| fi | |
| git fetch --force --tags origin | |
| TAG_COMMIT="$(git rev-list -n 1 "$TAG")" | |
| if [[ -z "$TAG_COMMIT" ]]; then | |
| echo "Unable to resolve commit for $TAG." >&2 | |
| exit 1 | |
| fi | |
| CURRENT_PACKAGE_DIGEST="$(git ls-tree -r "${{ steps.source.outputs.sha }}" -- "${PACKAGE_PATHS[@]}" | sha256sum | awk '{print $1}')" | |
| TAG_PACKAGE_DIGEST="$(git ls-tree -r "$TAG_COMMIT" -- "${PACKAGE_PATHS[@]}" | sha256sum | awk '{print $1}')" | |
| if [[ "$TAG_PACKAGE_DIGEST" != "$CURRENT_PACKAGE_DIGEST" ]]; then | |
| echo "$TAG exists but does not contain the current Spec Kit package. Bump package.json and extension.yml together before publishing." >&2 | |
| exit 1 | |
| fi | |
| # Bind installation to the exact commit whose package digest was verified. | |
| # Keep the human-readable tag only as reporting metadata. | |
| ARCHIVE_URL="https://github.com/${GITHUB_REPOSITORY}/archive/${TAG_COMMIT}.zip" | |
| echo "tag=$TAG" >> "$GITHUB_OUTPUT" | |
| echo "archive_url=$ARCHIVE_URL" >> "$GITHUB_OUTPUT" | |
| - name: Smoke-test exact release archive | |
| env: | |
| ARCHIVE_URL: ${{ steps.release.outputs.archive_url }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| workdir="$(mktemp -d)" | |
| trap 'rm -rf "$workdir"' EXIT | |
| cd "$workdir" | |
| specify init --here --force --non-interactive --ignore-agent-tools | |
| # Direct --from installs intentionally require explicit user consent. | |
| # Provide exactly one affirmative response for this verified release URL. | |
| printf 'y\n' | specify extension add hol-guard --from "$ARCHIVE_URL" | |
| specify extension list | tee installed-extensions.txt | |
| grep -F "hol-guard" installed-extensions.txt | |
| - name: Report verified release | |
| env: | |
| TAG: ${{ steps.release.outputs.tag }} | |
| ARCHIVE_URL: ${{ steps.release.outputs.archive_url }} | |
| run: | | |
| echo "Verified HOL Guard Spec Kit extension release $TAG" | |
| echo "Archive: $ARCHIVE_URL" |