diff --git a/.github/workflows/actions.lock b/.github/workflows/actions.lock new file mode 100644 index 0000000..5251abc --- /dev/null +++ b/.github/workflows/actions.lock @@ -0,0 +1,134 @@ +# This file is machine-generated by `gh actions-lock`. +# Do not edit by hand; run `gh actions-lock` to update. +# Docs: https://gh.io/actions-lockfile +version: 'v0.0.2' +workflows: + '.github/workflows/codeql.yml': + - 'actions/checkout@v7.0.1' + - 'github/codeql-action@v4.37.9' + '.github/workflows/container-build.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/dependabot-automerge.yml': + - 'dependabot/fetch-metadata@v3.1.0' + '.github/workflows/dogfood-gate.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/e2e.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/estate-rules.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/guix-policy.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/openssf-compliance.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/pages.yml': + - 'actions/checkout@v7.0.1' + - 'actions/deploy-pages@v5.0.1' + - 'actions/upload-pages-artifact@v5.0.0' + '.github/workflows/push-email-notify.yml': + - 'hyperpolymath/smtp-notify-action@v0.2.0' + '.github/workflows/quality.yml': + - 'actions/checkout@v7.0.1' + - 'editorconfig-checker/action-editorconfig-checker@v3.0.0' + '.github/workflows/release.yml': + - 'actions/attest-build-provenance@v4.2.2' + - 'actions/checkout@v7.0.1' + - 'actions/upload-artifact@v7.0.1' + - 'softprops/action-gh-release@v3.0.3' + '.github/workflows/rhodibot.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/runtime-policy.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/security-policy.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/sonarqube.yml': + - 'actions/checkout@v7.0.1' + - 'sonarsource/sonarqube-scan-action@v8.2.1' + '.github/workflows/static-analysis-gate.yml': + - 'actions/checkout@v7.0.1' + - 'actions/download-artifact@v8.0.1' + - 'actions/upload-artifact@v7.0.1' + - 'erlef/setup-beam@v1.24.1' + '.github/workflows/wellknown-enforcement.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/workflow-linter.yml': + - 'actions/checkout@v7.0.1' +dependencies: + 'actions/attest-build-provenance@v4.2.2': + ref: 'v4.2.2' + commit: 'sha1-4d101475d8b20a2381f78447822ac1eab6504dd8' + owner_id: 44036562 + repo_id: 760702757 + uses: + - 'actions/attest@508db95dd578ae2727ebd6217d5ba78e4fbda05d' + 'actions/attest@508db95dd578ae2727ebd6217d5ba78e4fbda05d': + ref: 'v4.2.1' + commit: 'sha1-508db95dd578ae2727ebd6217d5ba78e4fbda05d' + owner_id: 44036562 + repo_id: 760701061 + 'actions/checkout@v7.0.1': + ref: 'v7.0.1' + commit: 'sha1-3d3c42e5aac5ba805825da76410c181273ba90b1' + owner_id: 44036562 + repo_id: 197814629 + 'actions/deploy-pages@v5.0.1': + ref: 'v5.0.1' + commit: 'sha1-368f82528645a54fb793d4d04e342629a3f51346' + owner_id: 44036562 + repo_id: 438112499 + 'actions/download-artifact@v8.0.1': + ref: 'v8.0.1' + commit: 'sha1-3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c' + owner_id: 44036562 + repo_id: 192626254 + 'actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f': + ref: 'v7.0.0' + commit: 'sha1-bbbca2ddaa5d8feaa63e36b76fdaad77386f024f' + owner_id: 44036562 + repo_id: 192625955 + 'actions/upload-artifact@v7.0.1': + ref: 'v7.0.1' + commit: 'sha1-043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' + owner_id: 44036562 + repo_id: 192625955 + 'actions/upload-pages-artifact@v5.0.0': + ref: 'v5.0.0' + commit: 'sha1-fc324d3547104276b827a68afc52ff2a11cc49c9' + owner_id: 44036562 + repo_id: 496012378 + uses: + - 'actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f' + 'dependabot/fetch-metadata@v3.1.0': + ref: 'v3.1.0' + commit: 'sha1-25dd0e34f4fe68f24cc83900b1fe3fe149efef98' + owner_id: 27347476 + repo_id: 371068214 + 'editorconfig-checker/action-editorconfig-checker@v3.0.0': + ref: 'v3.0.0' + commit: 'sha1-51f63319f592f97930c73d9c46184d20bd206393' + owner_id: 26415196 + repo_id: 297874902 + 'erlef/setup-beam@v1.24.1': + ref: 'v1.24.1' + commit: 'sha1-54075bcc5e249e4758d363f27d099f55d843f124' + owner_id: 47606891 + repo_id: 331103973 + 'github/codeql-action@v4.37.9': + ref: 'v4.37.9' + commit: 'sha1-cdf488f595d80d6e07e03d4674febd5ab45fa938' + owner_id: 9919 + repo_id: 259445878 + 'hyperpolymath/smtp-notify-action@v0.2.0': + ref: 'v0.2.0' + commit: 'sha1-ede1191ef6ff3ac02c4f4d9efdf837ee517e11d7' + owner_id: 6759885 + repo_id: 1352485172 + 'softprops/action-gh-release@v3.0.3': + ref: 'v3.0.3' + commit: 'sha1-efb35369e0ad2afab669f228072c1b0d510eae64' + owner_id: 2242 + repo_id: 204253808 + 'sonarsource/sonarqube-scan-action@v8.2.1': + ref: 'v8.2.1' + commit: 'sha1-22918119ff8e1ca75a623e15c8296b6ea4fbe28f' + owner_id: 545988 + repo_id: 366408409 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index a71856c..65d84d9 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 name: CodeQL Security Analysis on: @@ -34,13 +35,13 @@ jobs: build-mode: none steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@v7.0.1 - name: Initialize CodeQL - uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v3 + uses: github/codeql-action/init@v4.37.9 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v3 + uses: github/codeql-action/analyze@v4.37.9 with: category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/container-build.yml b/.github/workflows/container-build.yml index e578cd2..fa6c089 100644 --- a/.github/workflows/container-build.yml +++ b/.github/workflows/container-build.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 name: container build on: @@ -31,7 +32,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@v7.0.1 - name: Tooling check run: | diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml index 652ace4..27c43a2 100644 --- a/.github/workflows/dependabot-automerge.yml +++ b/.github/workflows/dependabot-automerge.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # # dependabot-automerge.yml — enable GitHub's native auto-merge on @@ -49,13 +50,13 @@ permissions: jobs: automerge: # Only run for PRs actually authored by Dependabot. - if: github.actor == 'dependabot[bot]' && github.event.pull_request.user.login == 'dependabot[bot]' + if: github.actor_id == '49699333' && github.event.pull_request.user.login == 'dependabot[bot]' runs-on: ubuntu-latest timeout-minutes: 15 steps: - name: Fetch Dependabot metadata id: meta - uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0 + uses: dependabot/fetch-metadata@v3.1.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} # --- Policy gate ------------------------------------------------------- diff --git a/.github/workflows/dogfood-gate.yml b/.github/workflows/dogfood-gate.yml index 1358cd1..ebe3202 100644 --- a/.github/workflows/dogfood-gate.yml +++ b/.github/workflows/dogfood-gate.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) # @@ -30,7 +31,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@v7.0.1 - name: Check for A2ML files id: detect @@ -71,7 +72,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@v7.0.1 - name: Check for K9 files id: detect @@ -117,7 +118,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@v7.0.1 - name: Scan for invisible characters id: lint @@ -213,7 +214,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@v7.0.1 - name: Check for Groove manifest id: groove @@ -272,7 +273,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@v7.0.1 - name: Check and validate eclexiaiser manifest id: eclex @@ -324,7 +325,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@v7.0.1 - name: Generate dogfooding scorecard run: | diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 39da946..b3b60a2 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) # @@ -43,7 +44,7 @@ jobs: timeout-minutes: 15 steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@v7.0.1 - name: Run E2E harness run: | if [ -f tests/e2e.sh ]; then diff --git a/.github/workflows/estate-rules.yml b/.github/workflows/estate-rules.yml index 140ca4c..38b523f 100644 --- a/.github/workflows/estate-rules.yml +++ b/.github/workflows/estate-rules.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) # @@ -26,7 +27,7 @@ jobs: timeout-minutes: 15 steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@v7.0.1 - name: Root shape allowlist run: bash scripts/check-root-shape.sh . - name: AsciiDoc by default (no .md under docs/) diff --git a/.github/workflows/governance.yml b/.github/workflows/governance.yml index 724d9ed..0133d28 100644 --- a/.github/workflows/governance.yml +++ b/.github/workflows/governance.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 name: Governance diff --git a/.github/workflows/guix-policy.yml b/.github/workflows/guix-policy.yml index f8e3fff..2ddf657 100644 --- a/.github/workflows/guix-policy.yml +++ b/.github/workflows/guix-policy.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 name: Guix Package Policy on: @@ -21,7 +22,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@v7.0.1 - name: Enforce Guix-only package policy run: | # Guix is the sole package manager estate-wide. Guix is BANNED. diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index fe0ed88..a51dbd3 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # # Standalone Hypatia security scan (push / PR / weekly). This is NOT a duplicate @@ -26,4 +27,4 @@ permissions: jobs: scan: - uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@8f2ee50841e216cd8c192eeb68953118190f105c + uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@da2c748aad55c1a1dcba00b60fe4a35017bc6540 diff --git a/.github/workflows/instant-sync.yml b/.github/workflows/instant-sync.yml index 3c67981..bf3f085 100644 --- a/.github/workflows/instant-sync.yml +++ b/.github/workflows/instant-sync.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # Instant Forge Sync - Triggers propagation to all forges on push/release name: Instant Sync diff --git a/.github/workflows/label-triage.yml b/.github/workflows/label-triage.yml index 9886e92..814a192 100644 --- a/.github/workflows/label-triage.yml +++ b/.github/workflows/label-triage.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 name: Label Triage diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index c80b676..83ab941 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 name: Labels diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml index 9a96808..ebec909 100644 --- a/.github/workflows/mirror.yml +++ b/.github/workflows/mirror.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 name: Mirror to Git Forges on: diff --git a/.github/workflows/openssf-compliance.yml b/.github/workflows/openssf-compliance.yml index 5061adb..0eba1ed 100644 --- a/.github/workflows/openssf-compliance.yml +++ b/.github/workflows/openssf-compliance.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # OpenSSF Best Practices compliance gate — blocks PRs and pushes that lack # required files or still contain unfilled placeholder tokens. @@ -21,7 +22,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@v7.0.1 with: persist-credentials: false - name: Check SECURITY.md exists and has substance diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index aa6a989..71d0e28 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 name: GitHub Pages (Ddraig SSG) on: @@ -20,9 +21,9 @@ jobs: image: ghcr.io/stefan-hoeck/idris2-pack@sha256:f0758996a931fb35d9ecb1de273c4d59dabe2a09b433afc7e357f65a08b7e1ff steps: - name: Checkout Site - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + uses: actions/checkout@v7.0.1 - name: Checkout Ddraig SSG - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + uses: actions/checkout@v7.0.1 with: repository: hyperpolymath/ddraig-ssg path: .ddraig-ssg @@ -39,7 +40,7 @@ jobs: fi ./.ddraig-ssg/build/exec/ddraig build src _site https://hyperpolymath.github.io/${GITHUB_REPOSITORY#*/} - name: Upload artifact - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5 + uses: actions/upload-pages-artifact@v5.0.0 with: path: '_site' deploy: @@ -52,4 +53,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5 + uses: actions/deploy-pages@v5.0.1 diff --git a/.github/workflows/push-email-notify.yml b/.github/workflows/push-email-notify.yml index 0689291..676f498 100644 --- a/.github/workflows/push-email-notify.yml +++ b/.github/workflows/push-email-notify.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # Dormant push-email notification. ARMED by setting the repo variable # PUSH_EMAIL_ENABLED=true (the single on/off switch). Addresses are pre-filled; @@ -39,7 +40,7 @@ jobs: timeout-minutes: 5 steps: - name: Send push notification email - uses: hyperpolymath/smtp-notify-action@ede1191ef6ff3ac02c4f4d9efdf837ee517e11d7 # v0.2.0 + uses: hyperpolymath/smtp-notify-action@v0.2.0 with: server_address: ${{ secrets.SMTP_HOST }} server_port: ${{ secrets.SMTP_PORT }} diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 669fd2b..5f56e93 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 name: Code Quality on: @@ -22,7 +23,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@v7.0.1 - name: Check file permissions run: | find . -type f -perm /111 -name "*.sh" | head -10 || true @@ -34,7 +35,7 @@ jobs: run: | find . -type f -size +1M -not -path "./.git/*" | head -10 || echo "No large files" - name: EditorConfig check - uses: editorconfig-checker/action-editorconfig-checker@51f63319f592f97930c73d9c46184d20bd206393 # v3.0.0 + uses: editorconfig-checker/action-editorconfig-checker@v3.0.0 continue-on-error: true docs: runs-on: ubuntu-latest @@ -42,7 +43,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@v7.0.1 - name: Check documentation run: | MISSING="" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fa0bb6b..6631a1f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) # @@ -23,7 +24,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@v7.0.1 - name: Detect project type and build id: build run: | @@ -85,7 +86,7 @@ jobs: changelog: ${{ steps.cliff.outputs.content }} version: ${{ steps.version.outputs.version }} steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@v7.0.1 with: fetch-depth: 0 - name: Extract version from tag @@ -110,7 +111,7 @@ jobs: run: | git cliff --output CHANGELOG.md - name: Upload updated CHANGELOG.md - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@v7.0.1 with: name: changelog path: CHANGELOG.md @@ -125,7 +126,7 @@ jobs: id-token: write # mint the OIDC token attestation provenance is signed with attestations: write # write the build-provenance attestation (the "claim") steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@v7.0.1 # TODO: Download build artifacts if uploading to the release (pin # actions/download-artifact to a full commit SHA when enabling): # - uses: actions/download-artifact@ # vX.Y.Z @@ -133,7 +134,7 @@ jobs: # name: release-artifacts # path: artifacts/ - name: Create GitHub Release - uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v2 + uses: softprops/action-gh-release@v3.0.3 with: body: ${{ needs.changelog.outputs.changelog }} draft: false @@ -154,6 +155,6 @@ jobs: # (must match the `files:` uploaded above, e.g. artifacts/*). - name: Attest build provenance if: ${{ hashFiles('artifacts/*') != '' }} # skip until real artifacts are wired - uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 + uses: actions/attest-build-provenance@v4.2.2 with: subject-path: 'artifacts/*' diff --git a/.github/workflows/rhodibot.yml b/.github/workflows/rhodibot.yml index d7aabe9..26ca7a0 100644 --- a/.github/workflows/rhodibot.yml +++ b/.github/workflows/rhodibot.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # rhodibot.yml — RSR compliance CANARY (report-only) # @@ -34,7 +35,7 @@ jobs: timeout-minutes: 15 steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@v7.0.1 with: fetch-depth: 1 - name: Rhodibot — detect drift (no mutations) diff --git a/.github/workflows/runtime-policy.yml b/.github/workflows/runtime-policy.yml index 8727561..950aa8a 100644 --- a/.github/workflows/runtime-policy.yml +++ b/.github/workflows/runtime-policy.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # Runtime and package-manager policy check. # @@ -36,7 +37,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@v7.0.1 - name: Report runtime tier and reject mixed toolchains run: | diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index ba61ffe..bc73d4c 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # Rust CI — thin wrapper calling the shared estate reusable in # hyperpolymath/standards. Configure once, propagate everywhere. diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 7aae3c2..7b7adf4 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 name: OSSF Scorecard @@ -15,7 +16,7 @@ permissions: jobs: scorecard: - uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@8f2ee50841e216cd8c192eeb68953118190f105c + uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@da2c748aad55c1a1dcba00b60fe4a35017bc6540 permissions: contents: read security-events: write diff --git a/.github/workflows/secret-scanner.yml b/.github/workflows/secret-scanner.yml index aad0cde..bb1df33 100644 --- a/.github/workflows/secret-scanner.yml +++ b/.github/workflows/secret-scanner.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 name: Secret Scanner on: diff --git a/.github/workflows/security-policy.yml b/.github/workflows/security-policy.yml index 57ffa67..b71532d 100644 --- a/.github/workflows/security-policy.yml +++ b/.github/workflows/security-policy.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 name: Security Policy on: @@ -21,7 +22,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@v7.0.1 - name: Security checks run: | FAILED=false diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 3690484..c44845b 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # SonarQube Cloud (SonarCloud) static analysis. Analysis scope + exclusions live # in sonar-project.properties. Requires the SONAR_TOKEN repository secret @@ -24,10 +25,10 @@ jobs: timeout-minutes: 15 steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@v7.0.1 with: fetch-depth: 0 # full history for accurate new-code detection - name: SonarQube Scan - uses: SonarSource/sonarqube-scan-action@22918119ff8e1ca75a623e15c8296b6ea4fbe28f # v8.2.1 + uses: SonarSource/sonarqube-scan-action@v8.2.1 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/static-analysis-gate.yml b/.github/workflows/static-analysis-gate.yml index 8179fae..3ce342b 100644 --- a/.github/workflows/static-analysis-gate.yml +++ b/.github/workflows/static-analysis-gate.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # Static Analysis Gate — Required by branch protection rules. # Runs panic-attack and hypatia, deposits findings for gitbot-fleet learning. @@ -23,7 +24,7 @@ jobs: timeout-minutes: 15 steps: - name: Checkout repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@v7.0.1 with: fetch-depth: 0 - name: Install panic-attack (if available) @@ -70,7 +71,7 @@ jobs: TOTAL=$(jq '. | length' panic-attack-findings.json 2>/dev/null || echo 0) CRITICAL=$(jq '[.[] | select(.severity == "critical")] | length' panic-attack-findings.json 2>/dev/null || echo 0) HIGH=$(jq '[.[] | select(.severity == "high")] | length' panic-attack-findings.json 2>/dev/null || echo 0) - MEDIUM=$(jq '[.[] | select(.severity == "medium")] | length' panic-attack-findings.json 2>/dev/null || echo 0) + MEDIUM=$(jq '[.[] | select(.severity == "medium" or .severity == "warn")] | length' panic-attack-findings.json 2>/dev/null || echo 0) LOW=$(jq '[.[] | select(.severity == "low")] | length' panic-attack-findings.json 2>/dev/null || echo 0) echo "total=$TOTAL" >> "$GITHUB_OUTPUT" @@ -120,7 +121,7 @@ jobs: echo "" >> "$GITHUB_STEP_SUMMARY" echo "Skipped: panic-attack not available in this environment." >> "$GITHUB_STEP_SUMMARY" - name: Upload panic-attack findings - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@v7.0.1 with: name: panic-attack-findings path: panic-attack-findings.json @@ -147,13 +148,13 @@ jobs: timeout-minutes: 15 steps: - name: Checkout repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@v7.0.1 with: fetch-depth: 0 - name: Setup Elixir for Hypatia scanner id: beam continue-on-error: true - uses: erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124 # v1.18.2 + uses: erlef/setup-beam@v1.24.1 with: elixir-version: '1.19.4' otp-version: '28.3' @@ -206,7 +207,7 @@ jobs: TOTAL=$(jq '. | length' hypatia-findings.json 2>/dev/null || echo 0) CRITICAL=$(jq '[.[] | select(.severity == "critical")] | length' hypatia-findings.json 2>/dev/null || echo 0) HIGH=$(jq '[.[] | select(.severity == "high")] | length' hypatia-findings.json 2>/dev/null || echo 0) - MEDIUM=$(jq '[.[] | select(.severity == "medium")] | length' hypatia-findings.json 2>/dev/null || echo 0) + MEDIUM=$(jq '[.[] | select(.severity == "medium" or .severity == "warn")] | length' hypatia-findings.json 2>/dev/null || echo 0) LOW=$(jq '[.[] | select(.severity == "low")] | length' hypatia-findings.json 2>/dev/null || echo 0) echo "total=$TOTAL" >> "$GITHUB_OUTPUT" @@ -254,7 +255,7 @@ jobs: echo "" >> "$GITHUB_STEP_SUMMARY" echo "Skipped: Hypatia scanner not available in this environment." >> "$GITHUB_STEP_SUMMARY" - name: Upload hypatia findings - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@v7.0.1 with: name: hypatia-findings path: hypatia-findings.json @@ -273,7 +274,7 @@ jobs: timeout-minutes: 15 steps: - name: Checkout repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@v7.0.1 with: fetch-depth: 0 - name: Install panic-attack (if available) @@ -335,7 +336,7 @@ jobs: echo "" >> "$GITHUB_STEP_SUMMARY" echo "Skipped: panic-attack not available in this environment." >> "$GITHUB_STEP_SUMMARY" - name: Upload bridge report - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@v7.0.1 with: name: bridge-report path: bridge-report.json @@ -357,17 +358,17 @@ jobs: if: always() steps: - name: Download panic-attack findings - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v4 + uses: actions/download-artifact@v8.0.1 with: name: panic-attack-findings path: findings/ - name: Download hypatia findings - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v4 + uses: actions/download-artifact@v8.0.1 with: name: hypatia-findings path: findings/ - name: Download bridge report - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v4 + uses: actions/download-artifact@v8.0.1 with: name: bridge-report path: findings/ @@ -418,7 +419,7 @@ jobs: TOTAL=$(jq '.findings | length' findings/unified-findings.json) CRITICAL=$(jq '[.findings[] | select(.severity == "critical")] | length' findings/unified-findings.json) HIGH=$(jq '[.findings[] | select(.severity == "high")] | length' findings/unified-findings.json) - MEDIUM=$(jq '[.findings[] | select(.severity == "medium")] | length' findings/unified-findings.json) + MEDIUM=$(jq '[.findings[] | select(.severity == "medium" or .severity == "warn")] | length' findings/unified-findings.json) LOW=$(jq '[.findings[] | select(.severity == "low")] | length' findings/unified-findings.json) echo "total=$TOTAL" >> "$GITHUB_OUTPUT" @@ -427,7 +428,7 @@ jobs: echo "medium=$MEDIUM" >> "$GITHUB_OUTPUT" echo "low=$LOW" >> "$GITHUB_OUTPUT" - name: Upload unified findings (fleet scanner picks these up) - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@v7.0.1 with: name: unified-findings path: findings/unified-findings.json diff --git a/.github/workflows/wellknown-enforcement.yml b/.github/workflows/wellknown-enforcement.yml index 06e8f2d..6ce6a1f 100644 --- a/.github/workflows/wellknown-enforcement.yml +++ b/.github/workflows/wellknown-enforcement.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 name: Well-Known Standards (RFC 9116 + RSR) on: @@ -26,7 +27,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@v7.0.1 - name: RFC 9116 security.txt validation run: | SECTXT="" diff --git a/.github/workflows/workflow-linter.yml b/.github/workflows/workflow-linter.yml index db1795d..b81e49c 100644 --- a/.github/workflows/workflow-linter.yml +++ b/.github/workflows/workflow-linter.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # workflow-linter.yml - Validates GitHub workflows against RSR security standards # This workflow can be copied to other repos for consistent enforcement @@ -28,7 +29,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@v7.0.1 - name: Check SPDX Headers run: | diff --git a/Cargo.lock b/Cargo.lock index 7657665..2ffb575 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -114,6 +114,12 @@ dependencies = [ "syn", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -183,6 +189,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + [[package]] name = "http" version = "1.4.2" @@ -366,6 +378,16 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "itoa" version = "1.0.18" @@ -595,6 +617,19 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml_ng" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4db627b98b36d4203a7b458cf3573730f2bb591b28871d916dfa9efabfd41f" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "shlex" version = "2.0.1" @@ -665,6 +700,7 @@ dependencies = [ name = "squabble-fight" version = "0.1.0" dependencies = [ + "serde_yaml_ng", "squabble-core", ] @@ -817,6 +853,12 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "untrusted" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index b6c6edd..f22686c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,4 +22,5 @@ squabble-core = { path = "crates/squabble-core" } squabble-fight = { path = "crates/squabble-fight" } serde = { version = "1", features = ["derive"] } serde_json = "1" +serde_yaml_ng = "0.10" thiserror = "2" diff --git a/crates/squabble-cli/src/fetch.rs b/crates/squabble-cli/src/fetch.rs index 0ec3c47..6545421 100644 --- a/crates/squabble-cli/src/fetch.rs +++ b/crates/squabble-cli/src/fetch.rs @@ -24,8 +24,12 @@ use std::process::Command; #[derive(Debug, Deserialize)] struct RollupEntry { + // GitHub's rollup is a union: commit statuses use context/state, while + // check runs use name/conclusion. CodeRabbit commonly supplies a status. + #[serde(alias = "context")] name: String, status: Option, + #[serde(alias = "state")] conclusion: Option, /// `https://github.com/O/R/actions/runs//job/` — the only place /// the rollup exposes a job id, which is what the jobs API needs. @@ -270,6 +274,35 @@ pub fn run_with_greens(slug: &str, pr: &str) -> Result<(Gate, Vec), mod tests { use super::*; + #[test] + fn mixed_check_runs_and_commit_statuses_parse_without_losing_failures() { + let json = r#"{"baseRefName":"main","statusCheckRollup":[ + {"__typename":"CheckRun","name":"CI","status":"COMPLETED","conclusion":"SUCCESS"}, + {"__typename":"StatusContext","context":"CodeRabbit","state":"SUCCESS"}, + {"__typename":"StatusContext","context":"External review","state":"FAILURE"}, + {"__typename":"StatusContext","context":"Pending review","state":"PENDING"} + ]}"#; + let parsed: PrView = serde_json::from_str(json).expect("both GitHub rollup variants"); + assert_eq!(parsed.status_check_rollup.len(), 4); + assert_eq!( + parse_rollup(&parsed.status_check_rollup[0]), + CheckRun::Passed + ); + assert_eq!( + parse_rollup(&parsed.status_check_rollup[1]), + CheckRun::Passed + ); + assert_eq!( + parse_rollup(&parsed.status_check_rollup[2]), + CheckRun::Failed + ); + assert_eq!( + parse_rollup(&parsed.status_check_rollup[3]), + CheckRun::Pending + ); + assert!(greens_from_rollup(&parsed.status_check_rollup).is_empty()); + } + fn entry(name: &str, status: Option<&str>, conclusion: Option<&str>) -> RollupEntry { RollupEntry { name: name.to_string(), diff --git a/crates/squabble-cli/src/fight.rs b/crates/squabble-cli/src/fight.rs index 0455ad2..96b818f 100644 --- a/crates/squabble-cli/src/fight.rs +++ b/crates/squabble-cli/src/fight.rs @@ -13,9 +13,8 @@ use crate::fetch; use squabble_core::gate::Gate; use squabble_core::moves::Move; use squabble_core::outcome::Escalation; -use squabble_core::polarity::Evidence; use squabble_core::outcome::Outcome; -use squabble_core::polarity::{Applicability, Evidence, RepoDeclaration}; +use squabble_core::polarity::Evidence; use squabble_fight::context::RepoContext; use std::path::PathBuf; use std::process::ExitCode; diff --git a/crates/squabble-core/src/polarity.rs b/crates/squabble-core/src/polarity.rs index eead5fa..eafe953 100644 --- a/crates/squabble-core/src/polarity.rs +++ b/crates/squabble-core/src/polarity.rs @@ -183,7 +183,9 @@ pub struct SignatureSet { impl SignatureSet { /// Build a set from `(scanner, signature)` pairs. pub fn new(entries: Vec) -> Self { - SignatureSet { signatures: entries } + SignatureSet { + signatures: entries, + } } /// A one-scanner set — the shape the host had before multi-scanner @@ -687,7 +689,7 @@ mod tests { #[test] fn declared_and_unmatched_is_not_applicable() { let v = classify( - &[], // uninspectable; Axis 0 answers before steps are consulted + &[], // uninspectable; Axis 0 answers before steps are consulted &sigset(), &Applicability { runs_for_operator_types: vec!["platform_maintainer".into()], @@ -940,8 +942,14 @@ mod tests { assert!(set.is_usable()); assert_eq!( set.matching(&[ - StepOutcome { name: "Run Hypatia scan".into(), conclusion: StepConclusion::Skipped }, - StepOutcome { name: "Create stub findings".into(), conclusion: StepConclusion::Success }, + StepOutcome { + name: "Run Hypatia scan".into(), + conclusion: StepConclusion::Skipped + }, + StepOutcome { + name: "Create stub findings".into(), + conclusion: StepConclusion::Success + }, ]) .map(|s| s.scanner.as_str()), Some("hypatia") diff --git a/crates/squabble-fight/Cargo.toml b/crates/squabble-fight/Cargo.toml index e42827a..c336f42 100644 --- a/crates/squabble-fight/Cargo.toml +++ b/crates/squabble-fight/Cargo.toml @@ -12,3 +12,4 @@ rust-version.workspace = true [dependencies] squabble-core = { workspace = true } +serde_yaml_ng = { workspace = true } diff --git a/crates/squabble-fight/src/gate_triage.rs b/crates/squabble-fight/src/gate_triage.rs index 6675f5b..a86ba00 100644 --- a/crates/squabble-fight/src/gate_triage.rs +++ b/crates/squabble-fight/src/gate_triage.rs @@ -254,7 +254,10 @@ census = "33/33" // per key, every scanner would get hypatia's steps. let set = parse_signatures(DIRECTIVE); let pa = &set.signatures[1].signature; - assert_eq!(pa.skipped_steps, vec!["Run panic-attack assail".to_string()]); + assert_eq!( + pa.skipped_steps, + vec!["Run panic-attack assail".to_string()] + ); assert!( !pa.skipped_steps.contains(&"Run Hypatia scan".to_string()), "panic-attack must not inherit hypatia's steps" @@ -329,7 +332,11 @@ signature-success-steps = ["Create stub findings (when Hypatia unavailable)"] both.push_str("\nsignature-skipped-steps = [\"Run Hypatia scan\"]\n"); both.push_str("signature-success-steps = [\"Create stub findings\"]\n"); let set = parse_signatures(&both); - assert_eq!(set.signatures.len(), 2, "the legacy pair must not add a third"); + assert_eq!( + set.signatures.len(), + 2, + "the legacy pair must not add a third" + ); } // ---- ground truth ------------------------------------------------------ diff --git a/crates/squabble-fight/src/lib.rs b/crates/squabble-fight/src/lib.rs index 7b284de..f1b9923 100644 --- a/crates/squabble-fight/src/lib.rs +++ b/crates/squabble-fight/src/lib.rs @@ -327,6 +327,8 @@ mod tests { job_names: vec![], reusable_repos: reusable.iter().map(|s| s.to_string()).collect(), path_filtered, + retired_descriptile_policy: false, + empty_jobs: false, kind, } } diff --git a/crates/squabble-fight/src/workflows.rs b/crates/squabble-fight/src/workflows.rs index 4b310dd..0b0f534 100644 --- a/crates/squabble-fight/src/workflows.rs +++ b/crates/squabble-fight/src/workflows.rs @@ -45,6 +45,10 @@ pub struct WorkflowInfo { pub reusable_repos: Vec, /// True if the file declares an `on.*.paths` trigger filter. pub path_filtered: bool, + /// Executable policy contradicts the canonical descriptile location. + pub retired_descriptile_policy: bool, + /// A bare jobs block contains only whitespace or commented examples. + pub empty_jobs: bool, pub kind: WorkflowKind, } @@ -118,6 +122,25 @@ impl WorkflowFacts { let name = check.required_context.as_str(); let w = self.find_emitting(name)?; + if w.retired_descriptile_policy { + return Some(Move::FlagNonFunctionalGate { + check: name.to_string(), + evidence: format!( + "`{}` requires a retired descriptile path; reconcile its policy with .machine_readable/descriptiles/ and SD004 before retrying", + w.file + ), + }); + } + if w.empty_jobs { + return Some(Move::FlagNonFunctionalGate { + check: name.to_string(), + evidence: format!( + "`{}` contains only commented jobs; GitHub cannot create a check from this template", + w.file + ), + }); + } + // 1. Owned upstream: the job delegates to a reusable workflow living in // another repo. The fix belongs there, not on this PR. if let Some(repo) = w.reusable_repos.iter().find(|r| r.as_str() != slug) { @@ -252,10 +275,98 @@ fn parse_workflow(file: &str, text: &str) -> WorkflowInfo { job_names, reusable_repos, path_filtered, + retired_descriptile_policy: has_retired_descriptile_policy(text), + empty_jobs: has_empty_jobs(text), kind, } } +fn has_retired_descriptile_policy(text: &str) -> bool { + text.lines().any(|line| { + let line = line.trim(); + let scalar = line + .strip_prefix("- run:") + .or_else(|| line.strip_prefix("run:")); + // Decode YAML quoting before interpreting the shell command. Stripping + // delimiters alone loses escapes and can turn quoted prose into code. + let decoded; + let line = if let Some(scalar) = scalar { + let scalar = scalar.trim(); + if scalar.starts_with(['\'', '"']) { + let Ok(value) = serde_yaml_ng::from_str::(scalar) else { + return false; + }; + decoded = value; + decoded.trim() + } else { + scalar + } + } else { + line + }; + let mut words = line.split_whitespace().peekable(); + if matches!(words.peek(), Some(&"if" | &"elif" | &"while" | &"until")) { + words.next(); + } + if words.peek() == Some(&"!") { + words.next(); + } + let target = match words.next() { + Some("check_file") => words.next(), + Some("test" | "[" | "[[") => { + if words.peek() == Some(&"!") { + words.next(); + } + if matches!(words.next(), Some("-f" | "-e")) { + words.next() + } else { + None + } + } + _ => None, + }; + let Some(target) = target else { + return false; + }; + let target = target.trim_end_matches(';').trim_matches(['\'', '"']); + [ + "STATE", + "META", + "ECOSYSTEM", + "AGENTIC", + "NEUROSYM", + "PLAYBOOK", + "ANCHOR", + ] + .iter() + .any(|name| { + target == format!(".machine_readable/{name}.a2ml") + || target == format!(".machine_readable/6a2/{name}.a2ml") + }) + }) +} + +fn has_empty_jobs(text: &str) -> bool { + let mut in_jobs = false; + for line in text.lines() { + let trimmed = line.trim(); + if trimmed.is_empty() || trimmed.starts_with('#') { + continue; + } + if in_jobs { + // A non-comment indented value is outside this narrow diagnosis. + return !line.starts_with(char::is_whitespace); + } + if line.strip_prefix("jobs:").is_some_and(|rest| { + let rest = rest.trim(); + rest.is_empty() || rest.starts_with('#') + }) { + in_jobs = true; + } + } + in_jobs +} + /// Extract `owner/repo` from a reusable-workflow `uses:` line, i.e. one whose /// target contains `/.github/workflows/`. Action uses (`owner/repo@sha`) are /// ignored — they are not gate-emitting reusables. @@ -536,4 +647,60 @@ jobs: ) .is_none()); } + #[test] + fn retired_policy_is_a_gate_conflict_with_a_canonical_negative_control() { + let bad = "name: Compliance\njobs:\n compliance:\n steps:\n - run: test -f .machine_readable/STATE.a2ml\n"; + let parsed = parse_workflow("compliance.yml", bad); + assert!(parsed.retired_descriptile_policy); + let facts = WorkflowFacts { + workflows: vec![parsed], + }; + assert!(matches!( + facts.classify(&req("compliance", CheckRun::Missing), "owner/repo"), + Some(Move::FlagNonFunctionalGate { .. }) + )); + let fixed = bad.replace( + ".machine_readable/STATE", + ".machine_readable/descriptiles/STATE", + ); + assert!(!parse_workflow("compliance.yml", &fixed).retired_descriptile_policy); + assert!(!has_retired_descriptile_policy( + "# test -f .machine_readable/STATE.a2ml" + )); + assert!(!has_retired_descriptile_policy( + "- run: echo 'test -f .machine_readable/STATE.a2ml'" + )); + for scalar in [ + r#"run: "test -f .machine_readable/STATE.a2ml""#, + r#"run: 'test -f .machine_readable/STATE.a2ml'"#, + r#"run: "test\x20-f\u0020.machine_readable/STATE.a2ml""#, + r#"run: "test -f \".machine_readable/STATE.a2ml\"""#, + ] { + assert!(has_retired_descriptile_policy(scalar), "{scalar}"); + } + assert!(!has_retired_descriptile_policy( + r#"- run: "printf '%s\n' '# test -f .machine_readable/STATE.a2ml'""# + )); + } + + #[test] + fn commented_jobs_cannot_supply_a_check() { + let template = "name: E2E\njobs:\n # test:\n # runs-on: ubuntu-latest\n"; + let parsed = parse_workflow("e2e.yml", template); + assert!(parsed.empty_jobs); + let facts = WorkflowFacts { + workflows: vec![parsed], + }; + assert!(matches!( + facts.classify(&req("E2E", CheckRun::Missing), "owner/repo"), + Some(Move::FlagNonFunctionalGate { .. }) + )); + assert!(!has_empty_jobs("jobs:\n test:\n steps: []\n")); + assert!(!has_empty_jobs("# jobs:\n")); + assert!(has_empty_jobs("jobs: # template\n # test:\n")); + assert!(!has_empty_jobs( + "jobs: # real jobs\n test:\n steps: []\n" + )); + assert!(!has_empty_jobs("jobs: { test: {} }\n")); + } }