docs: reconcile repo hygiene checkpoint #81
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: Release | |
| on: | |
| push: | |
| branches: | |
| - main | |
| workflow_dispatch: | |
| inputs: | |
| recovery_reason: | |
| description: "Exact-ref automation/recovery note for running the trusted release lane" | |
| required: true | |
| type: string | |
| recovery_ref: | |
| description: "Commit SHA or tag for the exact revision being published or recovered" | |
| required: true | |
| type: string | |
| concurrency: | |
| group: release-${{ github.ref }} | |
| cancel-in-progress: false | |
| permissions: | |
| contents: read | |
| env: | |
| ELIXIR_VERSION: "1.19.5" | |
| OTP_VERSION: "28" | |
| jobs: | |
| release-please: | |
| name: Release Please | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| issues: write | |
| pull-requests: write | |
| outputs: | |
| release_created: ${{ steps.release.outputs.release_created || steps.manual_dispatch.outputs.release_created || 'false' }} | |
| tag_name: ${{ steps.release.outputs.tag_name || '' }} | |
| release_sha: ${{ steps.release.outputs.sha || '' }} | |
| steps: | |
| - name: Check out repository for Release Please | |
| if: ${{ github.event_name != 'workflow_dispatch' }} | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Confirm dispatch stays exact-ref only | |
| id: manual_dispatch | |
| if: ${{ github.event_name == 'workflow_dispatch' }} | |
| shell: bash | |
| run: | | |
| echo "release_created=false" >> "$GITHUB_OUTPUT" | |
| echo "workflow_dispatch bypasses Release Please and exists only to run the trusted publish lane for an exact immutable ref selected by release automation or recovery." | |
| - name: Record review-only Release Please PR posture | |
| if: ${{ github.event_name != 'workflow_dispatch' }} | |
| run: | | |
| echo "Release Please generated PRs are review-only. Checked-in version and changelog truth becomes authoritative only after the merged release commit crosses the protected hex-publish environment." | |
| echo "Push-triggered Release Please manages release PRs only; the exact-ref dispatch publish lane owns GitHub release/tag creation and Hex publish for the root lockspire-v<version> contract." | |
| - name: Run Release Please | |
| id: release | |
| if: ${{ github.event_name != 'workflow_dispatch' }} | |
| uses: ./.github/actions/release-please | |
| with: | |
| config-file: release-please-config.json | |
| manifest-file: .release-please-manifest.json | |
| skip-github-release: true | |
| - name: Record root release tag target for the merged release commit | |
| if: ${{ github.event_name != 'workflow_dispatch' && steps.release.outputs.release_created == 'true' && steps.release.outputs.sha == github.sha }} | |
| run: | | |
| echo "Release Please selected root tag ${{ steps.release.outputs.tag_name }} for the merged release commit. Protected publish proof begins only when the publish job enters the hex-publish environment on main." | |
| - name: Record stale Release Please release event | |
| if: ${{ github.event_name != 'workflow_dispatch' && steps.release.outputs.release_created == 'true' && steps.release.outputs.sha != github.sha }} | |
| run: | | |
| echo "Release Please created or finalized ${{ steps.release.outputs.tag_name }} for ${{ steps.release.outputs.sha }}, but this workflow is running for ${{ github.sha }}." | |
| echo "Skipping publish on this push prevents republishing an existing Hex package after a delayed Release Please tagging event." | |
| recovery-validation: | |
| name: Validate Recovery Ref | |
| if: ${{ github.event_name == 'workflow_dispatch' }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| outputs: | |
| checkout_ref: ${{ steps.validate.outputs.checkout_ref }} | |
| steps: | |
| - name: Check out repository for recovery validation | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| - name: Validate exact-ref dispatch inputs and lock to an immutable ref | |
| id: validate | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| test -n "${{ inputs.recovery_reason }}" | |
| recovery_ref="${{ inputs.recovery_ref }}" | |
| test -n "$recovery_ref" | |
| if [[ "$recovery_ref" =~ ^[0-9a-f]{40}$ ]]; then | |
| git cat-file -e "${recovery_ref}^{commit}" | |
| echo "checkout_ref=$recovery_ref" >> "$GITHUB_OUTPUT" | |
| elif git show-ref --verify --quiet "refs/tags/$recovery_ref"; then | |
| echo "checkout_ref=$recovery_ref" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "workflow_dispatch is exact-ref only and recovery_ref must be an exact 40-character commit SHA or an existing tag." | |
| exit 1 | |
| fi | |
| echo "workflow_dispatch is exact-ref only. It publishes the exact immutable ref selected in recovery_ref, and normal release intent still starts from a merged Release Please commit in the protected hex-publish environment." | |
| publish: | |
| name: Publish to Hex | |
| runs-on: ubuntu-latest | |
| needs: | |
| - release-please | |
| - recovery-validation | |
| if: ${{ always() && ((github.event_name == 'workflow_dispatch' && needs.recovery-validation.result == 'success') || (github.event_name != 'workflow_dispatch' && needs.release-please.outputs.release_created == 'true' && needs.release-please.outputs.release_sha == github.sha)) }} | |
| environment: hex-publish | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Check out repository for the merged release commit | |
| if: ${{ github.event_name != 'workflow_dispatch' }} | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Check out repository for recovery validation | |
| if: ${{ github.event_name == 'workflow_dispatch' }} | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| ref: ${{ needs.recovery-validation.outputs.checkout_ref }} | |
| - name: Confirm recovery checkout is detached to the validated immutable ref | |
| if: ${{ github.event_name == 'workflow_dispatch' }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| git checkout --detach HEAD | |
| echo "Recovered immutable ref: ${{ needs.recovery-validation.outputs.checkout_ref }}" | |
| - name: Record protected-boundary entry point | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
| echo "Protected publish proof starts now for dispatch ref ${{ needs.recovery-validation.outputs.checkout_ref }} inside the single hex-publish environment boundary." | |
| else | |
| echo "Protected publish proof starts now for merged release tag ${{ needs.release-please.outputs.tag_name }} inside the single hex-publish environment boundary." | |
| fi | |
| - name: Ensure dispatch GitHub release exists | |
| if: ${{ github.event_name == 'workflow_dispatch' }} | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| GH_REPO: ${{ github.repository }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| version="$(grep -E '^[[:space:]]*version:' mix.exs | head -1 | sed -E 's/.*"([^"]+)".*/\1/')" | |
| tag_name="lockspire-v${version}" | |
| release_title="lockspire: v${version}" | |
| if gh release view "$tag_name" >/dev/null 2>&1; then | |
| echo "GitHub release $tag_name already exists." | |
| exit 0 | |
| fi | |
| notes="$( | |
| awk -v version="$version" ' | |
| $0 ~ "^## \\[" version "\\]" { in_section = 1; next } | |
| in_section && $0 ~ "^## \\[" { exit } | |
| in_section { print } | |
| ' CHANGELOG.md | |
| )" | |
| test -n "$notes" | |
| gh release create "$tag_name" \ | |
| --target "$(git rev-parse HEAD)" \ | |
| --title "$release_title" \ | |
| --notes "$notes" | |
| - name: Set up Elixir and Erlang | |
| uses: erlef/setup-beam@fc68ffb90438ef2936bbb3251622353b3dcb2f93 # v1.24.0 | |
| with: | |
| elixir-version: ${{ env.ELIXIR_VERSION }} | |
| otp-version: ${{ env.OTP_VERSION }} | |
| - name: Install Hex and Rebar | |
| run: | | |
| mix local.hex --force | |
| mix local.rebar --force | |
| - name: Restore Mix cache | |
| uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 | |
| with: | |
| path: | | |
| deps | |
| _build | |
| key: ${{ runner.os }}-mix-release-v2-${{ env.OTP_VERSION }}-${{ env.ELIXIR_VERSION }}-${{ hashFiles('mix.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-mix-release-v2-${{ env.OTP_VERSION }}-${{ env.ELIXIR_VERSION }}- | |
| - name: Install dependencies | |
| run: mix deps.get | |
| - name: Run trusted release preflight inside protected environment | |
| env: | |
| HEX_API_KEY: ${{ secrets.HEX_API_KEY }} | |
| run: mix release.preflight | |
| - name: Publish package from the trusted lane | |
| env: | |
| HEX_API_KEY: ${{ secrets.HEX_API_KEY }} | |
| run: mix hex.publish --yes |