diff --git a/.github/workflows/actions.lock b/.github/workflows/actions.lock new file mode 100644 index 0000000..0dea24a --- /dev/null +++ b/.github/workflows/actions.lock @@ -0,0 +1,126 @@ +# 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/boj-build.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/codeql.yml': + - 'actions/checkout@v7.0.1' + - 'github/codeql-action@v4.37.4' + '.github/workflows/dependabot-automerge.yml': + - 'dependabot/fetch-metadata@v3.1.0' + '.github/workflows/dogfood-gate.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/estate-rules.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/governance.yml': [] + '.github/workflows/guix-nix-policy.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/hypatia-scan.yml': [] + '.github/workflows/instant-sync.yml': + - 'peter-evans/repository-dispatch@v4.0.1' + '.github/workflows/mirror.yml': [] + '.github/workflows/openssf-compliance.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/pages.yml': + - 'actions/checkout@v7.0.1' + - 'actions/deploy-pages@v5.0.0' + - 'actions/upload-pages-artifact@v5.0.0' + '.github/workflows/push-email-notify.yml': + - 'dawidd6/action-send-mail@v3.12.0' + '.github/workflows/quality.yml': + - 'actions/checkout@v7.0.1' + - 'editorconfig-checker/action-editorconfig-checker@v2.2.0' + '.github/workflows/release.yml': + - 'actions/checkout@v7.0.1' + - 'actions/upload-artifact@v7.0.1' + - 'softprops/action-gh-release@v3.0.2' + '.github/workflows/rhodibot.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/runtime-policy.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/rust-ci.yml': [] + '.github/workflows/scorecard.yml': [] + '.github/workflows/secret-scanner.yml': [] + '.github/workflows/security-policy.yml': + - 'actions/checkout@v7.0.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/trope-check.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/wellknown-enforcement.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/workflow-linter.yml': + - 'actions/checkout@v7.0.1' +dependencies: + 'actions/checkout@v7.0.1': + ref: 'v7.0.1' + commit: 'sha1-3d3c42e5aac5ba805825da76410c181273ba90b1' + owner_id: 44036562 + repo_id: 197814629 + 'actions/deploy-pages@v5.0.0': + ref: 'v5.0.0' + commit: 'sha1-cd2ce8fcbc39b97be8ca5fce6e763baed58fa128' + 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' + 'dawidd6/action-send-mail@v3.12.0': + ref: 'v3.12.0' + commit: 'sha1-2e600f3776677e493bf0da7aaec39a586d19a5ed' + owner_id: 9713907 + repo_id: 222439721 + 'dependabot/fetch-metadata@v3.1.0': + ref: 'v3.1.0' + commit: 'sha1-25dd0e34f4fe68f24cc83900b1fe3fe149efef98' + owner_id: 27347476 + repo_id: 371068214 + 'editorconfig-checker/action-editorconfig-checker@v2.2.0': + ref: 'v2.2.0' + commit: 'sha1-840e866d93b8e032123c23bac69dece044d4d84c' + 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.4': + ref: 'v4.37.4' + commit: 'sha1-f205ea1c3313d32999d8d6a48b4f6530d4437b38' + owner_id: 9919 + repo_id: 259445878 + 'peter-evans/repository-dispatch@v4.0.1': + ref: 'v4.0.1' + commit: 'sha1-28959ce8df70de7be546dd1250a005dd32156697' + owner_id: 18365890 + repo_id: 220359305 + 'softprops/action-gh-release@v3.0.2': + ref: 'v3.0.2' + commit: 'sha1-3d0d9888cb7fd7b750713d6e236d1fcb99157228' + owner_id: 2242 + repo_id: 204253808 diff --git a/.github/workflows/boj-build.yml b/.github/workflows/boj-build.yml index 3761093..0543fe7 100644 --- a/.github/workflows/boj-build.yml +++ b/.github/workflows/boj-build.yml @@ -1,4 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. # # OPTIONAL: BoJ Server Build Trigger # This workflow notifies a BoJ Server instance when code is pushed. @@ -16,10 +17,9 @@ jobs: trigger-boj: runs-on: ubuntu-latest timeout-minutes: 15 - if: ${{ vars.BOJ_SERVER_URL != '' || secrets.BOJ_SERVER_URL != '' }} steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@v7.0.1 - name: Trigger BoJ Server (Casket/ssg-mcp) env: BOJ_URL: ${{ secrets.BOJ_SERVER_URL || vars.BOJ_SERVER_URL }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 08dcae7..70db89d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,4 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. name: "๐ŸŸก Check: CodeQL security analysis" on: push: @@ -30,13 +31,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@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v3.28.1 + uses: github/codeql-action/init@v4.37.4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v3.28.1 + uses: github/codeql-action/analyze@v4.37.4 with: category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml index c2ada47..80fd739 100644 --- a/.github/workflows/dependabot-automerge.yml +++ b/.github/workflows/dependabot-automerge.yml @@ -1,4 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. # # dependabot-automerge.yml โ€” enable GitHub's native auto-merge on # Dependabot pull requests that match a declared severity / ecosystem @@ -51,7 +52,7 @@ jobs: 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 54d3eb5..7e93368 100644 --- a/.github/workflows/dogfood-gate.yml +++ b/.github/workflows/dogfood-gate.yml @@ -1,4 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. # Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) # # dogfood-gate.yml โ€” Hyperpolymath Dogfooding Quality Gate @@ -26,7 +27,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 @@ -67,7 +68,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 @@ -113,7 +114,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 @@ -178,7 +179,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 @@ -237,7 +238,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 @@ -288,7 +289,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/estate-rules.yml b/.github/workflows/estate-rules.yml index 188f428..7ed107a 100644 --- a/.github/workflows/estate-rules.yml +++ b/.github/workflows/estate-rules.yml @@ -1,4 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. # Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) # # Estate Rules โ€” enforces hyperpolymath estate-wide conventions: @@ -22,7 +23,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 72fe9b6..820d071 100644 --- a/.github/workflows/governance.yml +++ b/.github/workflows/governance.yml @@ -1,4 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. name: "๐ŸŸก Check: Governance metadata" on: @@ -10,7 +11,8 @@ on: permissions: contents: read + actions: read jobs: governance: - uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@d7c22711e830e1f383846472f6e9b99debdb201e + uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@bd0df9ead7faf0cdfe0e13e7966d91e28d0101d4 diff --git a/.github/workflows/guix-nix-policy.yml b/.github/workflows/guix-nix-policy.yml index ac7d094..b42d5ca 100644 --- a/.github/workflows/guix-nix-policy.yml +++ b/.github/workflows/guix-nix-policy.yml @@ -1,4 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. name: "๐Ÿ”ด Gate: Guix-only package policy" on: push: @@ -20,9 +21,10 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@v7.0.1 - name: Enforce Guix primary / Nix fallback run: | + FAILED=false # Check for package manager files HAS_GUIX=$(find . -name "*.scm" -o -name ".guix-channel" -o -name "guix.scm" 2>/dev/null | head -1) HAS_NIX=$(find . -name "*.nix" 2>/dev/null | head -1) @@ -30,7 +32,9 @@ jobs: # Block new package-lock.json, yarn.lock, Gemfile.lock, etc. NEW_LOCKS=$(git diff --name-only --diff-filter=A HEAD~1 2>/dev/null | grep -E 'package-lock\.json|yarn\.lock|Gemfile\.lock|Pipfile\.lock|poetry\.lock|cargo\.lock' || true) if [ -n "$NEW_LOCKS" ]; then - echo "โš ๏ธ Lock files detected. Prefer Guix manifests for reproducibility." + echo "โŒ Foreign lock files added โ€” this repo is Guix-only:" + echo "$NEW_LOCKS" + FAILED=true fi # Prefer Guix, fallback to Nix @@ -42,4 +46,9 @@ jobs: echo "โ„น๏ธ Consider adding guix.scm or flake.nix for reproducible builds" fi + if [ "$FAILED" = true ]; then + echo "Guix-only package policy violated โ€” see above." + exit 1 + fi + echo "โœ… Package policy check passed" diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index 2abb40c..b9d3191 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -1,4 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. name: "๐ŸŸก Check: Hypatia baseline security scan" on: @@ -12,8 +13,9 @@ on: permissions: contents: read - security-events: read + actions: read + security-events: write jobs: scan: - uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@d7c22711e830e1f383846472f6e9b99debdb201e + uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@bd0df9ead7faf0cdfe0e13e7966d91e28d0101d4 diff --git a/.github/workflows/instant-sync.yml b/.github/workflows/instant-sync.yml index c458353..aed4f69 100644 --- a/.github/workflows/instant-sync.yml +++ b/.github/workflows/instant-sync.yml @@ -1,4 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. # Instant Forge Sync - Triggers propagation to all forges on push/release name: "โš™ Auto: Instant sync" on: @@ -29,7 +30,7 @@ jobs: fi - name: Trigger Propagation if: steps.gate.outputs.present == 'true' - uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v3 + uses: peter-evans/repository-dispatch@v4.0.1 with: token: ${{ secrets.FARM_DISPATCH_TOKEN }} repository: hyperpolymath/.git-private-farm diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml index acb9eb1..3135aaf 100644 --- a/.github/workflows/mirror.yml +++ b/.github/workflows/mirror.yml @@ -1,4 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. name: "โš™ Auto: Mirror to forges" on: push: @@ -8,5 +9,5 @@ permissions: contents: read jobs: mirror: - uses: hyperpolymath/standards/.github/workflows/mirror-reusable.yml@09e7023d24682621bea4e11965a1ef5e87d86c3b + uses: hyperpolymath/standards/.github/workflows/mirror-reusable.yml@bd0df9ead7faf0cdfe0e13e7966d91e28d0101d4 secrets: inherit diff --git a/.github/workflows/openssf-compliance.yml b/.github/workflows/openssf-compliance.yml index c64a23e..e62d1e5 100644 --- a/.github/workflows/openssf-compliance.yml +++ b/.github/workflows/openssf-compliance.yml @@ -1,4 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. # OpenSSF Best Practices compliance gate โ€” blocks PRs and pushes that lack # required files or still contain unfilled placeholder tokens. name: "๐ŸŸก Check: OpenSSF best-practices" @@ -17,7 +18,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 bec6d98..bd97b5a 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. + name: GitHub Pages (Ddraig SSG) on: push: @@ -37,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@v5 + uses: actions/upload-pages-artifact@v5.0.0 with: path: '_site' deploy: @@ -50,4 +53,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v5 + uses: actions/deploy-pages@v5.0.0 diff --git a/.github/workflows/push-email-notify.yml b/.github/workflows/push-email-notify.yml index 3421edd..b69c87f 100644 --- a/.github/workflows/push-email-notify.yml +++ b/.github/workflows/push-email-notify.yml @@ -1,4 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. # Dormant push-email notification. ARMED by setting the repo variable # PUSH_EMAIL_ENABLED=true (the single on/off switch). Addresses are pre-filled; # sending needs the org SMTP secrets (SMTP_HOST/PORT/USER/PASS). Inherited by @@ -15,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Send push notification email - uses: dawidd6/action-send-mail@2e600f3776677e493bf0da7aaec39a586d19a5ed # pinned + uses: dawidd6/action-send-mail@v3.12.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 d74f357..0a0d1a9 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -1,4 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. name: "๐Ÿ”ด Gate: Code quality (fmt/lint/test)" on: push: @@ -21,17 +22,10 @@ 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 - - name: Check for secrets - uses: trufflesecurity/trufflehog@6f3c981e7b77f235fd2702dd74af25fc4b72bf11 # v3.93.3 - with: - path: ./ - base: ${{ github.event.pull_request.base.sha || github.event.before }} - head: ${{ github.sha }} - continue-on-error: true - name: Check TODO/FIXME run: | echo "=== TODOs ===" @@ -40,15 +34,16 @@ 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@840e866d93b8e032123c23bac69dece044d4d84c # v2.2.0 - continue-on-error: true + uses: editorconfig-checker/action-editorconfig-checker@v2.2.0 + - name: Gate falsifiability canaries (R10) + run: bash scripts/test-gate-falsifiability.sh docs: runs-on: ubuntu-latest timeout-minutes: 15 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 5362cdb..f1dff16 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. # Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) # # Release workflow โ€” triggered by version tags (v*). @@ -19,7 +20,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: | @@ -80,7 +81,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 @@ -105,7 +106,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 @@ -118,14 +119,14 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@v7.0.1 # TODO: Download build artifacts if uploading to the release # - uses: actions/download-artifact@v4 # with: # name: release-artifacts # path: artifacts/ - name: Create GitHub Release - uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v2 + uses: softprops/action-gh-release@v3.0.2 with: body: ${{ needs.changelog.outputs.changelog }} draft: false diff --git a/.github/workflows/rhodibot.yml b/.github/workflows/rhodibot.yml index bb9fdb0..974e643 100644 --- a/.github/workflows/rhodibot.yml +++ b/.github/workflows/rhodibot.yml @@ -1,4 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. # rhodibot.yml โ€” AUDIT MODE (report-only) # # rhodibot is temporarily in AUDIT MODE in this repo: it DETECTS RSR-compliance drift @@ -21,7 +22,7 @@ jobs: timeout-minutes: 10 steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@v7.0.1 with: fetch-depth: 1 - name: Rhodibot โ€” AUDIT (detect + log; NO mutation, NO PR) diff --git a/.github/workflows/rsr-antipattern.yml b/.github/workflows/rsr-antipattern.yml deleted file mode 100644 index 96184c5..0000000 --- a/.github/workflows/rsr-antipattern.yml +++ /dev/null @@ -1,14 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# RSR Anti-Pattern Check - Uses reusable workflow from standards - -name: "๐Ÿ”ด Gate: RSR anti-pattern scan" -on: - push: - branches: [main, master, develop] - pull_request: - branches: [main, master, develop] -permissions: - contents: read -jobs: - antipattern-check: - uses: hyperpolymath/standards/.github/workflows/rsr-antipattern-reusable.yml@8fbf5cd88b43a699ef09fca2f83a852281e269dd # standards main pin (freezes current behaviour) diff --git a/.github/workflows/runtime-policy.yml b/.github/workflows/runtime-policy.yml index 8727561..8e1464b 100644 --- a/.github/workflows/runtime-policy.yml +++ b/.github/workflows/runtime-policy.yml @@ -1,4 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. # Runtime and package-manager policy check. # # Authority: hyperpolymath/standards LANGUAGE-POLICY.adoc ยง1. @@ -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 825cac8..d0e5139 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -1,4 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. # Rust CI โ€” thin wrapper calling the shared estate reusable in # hyperpolymath/standards. Configure once, propagate everywhere. # See: docs/CI-REUSABLE-WORKFLOWS.adoc in standards. @@ -14,4 +15,4 @@ concurrency: cancel-in-progress: true jobs: rust-ci: - uses: hyperpolymath/standards/.github/workflows/rust-ci-reusable.yml@e9c8888769a703924cc3c0d717900960d78aea00 + uses: hyperpolymath/standards/.github/workflows/rust-ci-reusable.yml@bd0df9ead7faf0cdfe0e13e7966d91e28d0101d4 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 7c96fda..babc9cd 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -1,4 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. name: "โšช Advisory: OpenSSF Scorecard (score)" on: @@ -13,7 +14,7 @@ permissions: jobs: scorecard: - uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@d135b05bfc647d0c0fbfedc7e80f37ea50f49236 + uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@bd0df9ead7faf0cdfe0e13e7966d91e28d0101d4 permissions: contents: read security-events: write diff --git a/.github/workflows/secret-scanner.yml b/.github/workflows/secret-scanner.yml index b3394ad..40c70d9 100644 --- a/.github/workflows/secret-scanner.yml +++ b/.github/workflows/secret-scanner.yml @@ -1,4 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. name: "๐Ÿ”ด Gate: Secret scan" on: pull_request: @@ -20,5 +21,5 @@ jobs: contents: read pull-requests: write actions: read - uses: hyperpolymath/standards/.github/workflows/secret-scanner-reusable.yml@db12a6ad3ef9076d5f7bdcf98d7d15cf8547555a + uses: hyperpolymath/standards/.github/workflows/secret-scanner-reusable.yml@bd0df9ead7faf0cdfe0e13e7966d91e28d0101d4 secrets: inherit diff --git a/.github/workflows/security-policy.yml b/.github/workflows/security-policy.yml index 3263442..9fe9fb5 100644 --- a/.github/workflows/security-policy.yml +++ b/.github/workflows/security-policy.yml @@ -1,4 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. name: "๐Ÿ”ด Gate: Security policy present" on: push: @@ -20,7 +21,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 @@ -28,15 +29,17 @@ jobs: # Block MD5/SHA1 for security (allow for checksums/caching) WEAK_CRYPTO=$(grep -rE 'md5\(|sha1\(' --include="*.py" --include="*.rb" --include="*.js" --include="*.ts" --include="*.go" --include="*.rs" . 2>/dev/null | grep -v 'checksum\|cache\|test\|spec' | head -5 || true) if [ -n "$WEAK_CRYPTO" ]; then - echo "โš ๏ธ Weak crypto (MD5/SHA1) detected. Use SHA256+ for security:" + echo "โŒ Weak crypto (MD5/SHA1) detected. Use SHA256+ for security:" echo "$WEAK_CRYPTO" + FAILED=true fi # Block HTTP URLs (except localhost) HTTP_URLS=$(grep -rE 'http://[^l][^o][^c]' --include="*.py" --include="*.js" --include="*.ts" --include="*.go" --include="*.rs" --include="*.yaml" --include="*.yml" . 2>/dev/null | grep -v 'localhost\|127.0.0.1\|example\|test\|spec' | head -5 || true) if [ -n "$HTTP_URLS" ]; then - echo "โš ๏ธ HTTP URLs found. Use HTTPS:" + echo "โŒ HTTP URLs found. Use HTTPS:" echo "$HTTP_URLS" + FAILED=true fi # Block hardcoded secrets patterns diff --git a/.github/workflows/static-analysis-gate.yml b/.github/workflows/static-analysis-gate.yml index 54f156f..ca8dc8e 100644 --- a/.github/workflows/static-analysis-gate.yml +++ b/.github/workflows/static-analysis-gate.yml @@ -1,7 +1,20 @@ # SPDX-License-Identifier: MPL-2.0 -# Static Analysis Gate โ€” Required by branch protection rules. +# This workflow is managed by gh actions-lock. +# Static analysis โ€” advisory CHECK, deliberately NOT a blocking gate. +# # Runs panic-attack and hypatia, deposits findings for gitbot-fleet learning. -name: "๐Ÿ”ด Gate: Static analysis" +# +# Tier: ๐ŸŸก CHECK. It was labelled ๐Ÿ”ด Gate for 99 runs and never once failed, +# because it cannot: it skips when its tools are absent, pipes findings through +# `|| true`, and carries continue-on-error. Two honest resolutions existed โ€” +# make it able to fail, or stop calling it a gate. The second is correct here: +# panic-attack has a known false-positive problem (panic-attack#171), much of it +# firing on tests and benches rather than shipping code, so blocking merges on +# it would train people to bypass the gate. Advisory is what it actually is. +# +# It was also NOT in any ruleset despite the old header claiming otherwise, so +# renaming it cannot strand a required context. +name: "๐ŸŸก CHECK: Static analysis" on: pull_request: branches: ['**'] @@ -19,7 +32,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) @@ -32,7 +45,7 @@ jobs: chmod +x /usr/local/bin/panic-attack echo "installed=true" >> "$GITHUB_OUTPUT" else - echo "::notice::panic-attack binary not available โ€” skipping assail" + echo "::warning::panic-attack binary NOT AVAILABLE โ€” assail DID NOT RUN. Empty findings below mean "not scanned", not "clean"." echo "installed=false" >> "$GITHUB_OUTPUT" fi - name: Run panic-attack assail @@ -94,9 +107,9 @@ jobs: echo "[]" > panic-attack-findings.json echo "## panic-attack assail" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY" - echo "Skipped: panic-attack not available in this environment." >> "$GITHUB_STEP_SUMMARY" + echo "**DID NOT RUN** โ€” panic-attack was not available in this environment. The empty result is absence of evidence, not evidence of absence." >> "$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 @@ -115,13 +128,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' @@ -139,7 +152,7 @@ jobs: fi echo "ready=true" >> "$GITHUB_OUTPUT" else - echo "::notice::Hypatia scanner not available โ€” skipping scan" + echo "::warning::Hypatia scanner NOT AVAILABLE โ€” scan DID NOT RUN. Empty findings below mean "not scanned", not "clean"." echo "ready=false" >> "$GITHUB_OUTPUT" fi - name: Run Hypatia scan @@ -198,9 +211,9 @@ jobs: echo "[]" > hypatia-findings.json echo "## Hypatia Scan" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY" - echo "Skipped: Hypatia scanner not available in this environment." >> "$GITHUB_STEP_SUMMARY" + echo "**DID NOT RUN** โ€” the Hypatia scanner was not available in this environment. The empty result is absence of evidence, not evidence of absence." >> "$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 @@ -219,7 +232,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) @@ -231,7 +244,7 @@ jobs: chmod +x /usr/local/bin/panic-attack echo "installed=true" >> "$GITHUB_OUTPUT" else - echo "::notice::panic-attack binary not available โ€” skipping Patch Bridge" + echo "::warning::panic-attack binary NOT AVAILABLE โ€” Patch Bridge DID NOT RUN. An empty CVE report below means "not triaged", not "no CVEs"." echo "installed=false" >> "$GITHUB_OUTPUT" fi - name: Run Patch Bridge triage @@ -279,9 +292,9 @@ jobs: echo '{"cves":[],"mitigated":0,"unmitigable":0,"concatenative":0,"informational":0}' > bridge-report.json echo "## Patch Bridge CVE Triage" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY" - echo "Skipped: panic-attack not available in this environment." >> "$GITHUB_STEP_SUMMARY" + echo "**DID NOT RUN** โ€” panic-attack was not available in this environment. The empty result is absence of evidence, not evidence of absence." >> "$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 @@ -303,17 +316,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/ @@ -373,7 +386,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/trope-check.yml b/.github/workflows/trope-check.yml index 1aa1a10..71132c9 100644 --- a/.github/workflows/trope-check.yml +++ b/.github/workflows/trope-check.yml @@ -1,4 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. # Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) # # Workbench: the nine effects stay consistent across vocabulary, trainer, map, README (bash; no Python). @@ -14,6 +15,6 @@ jobs: nine-effects-consistent: runs-on: ubuntu-latest steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@v7.0.1 - name: Nine effects consistent (vocabulary / trainer / map / README) run: bash tests/check-vocabulary.sh diff --git a/.github/workflows/wellknown-enforcement.yml b/.github/workflows/wellknown-enforcement.yml index 0b7e9af..4a8537e 100644 --- a/.github/workflows/wellknown-enforcement.yml +++ b/.github/workflows/wellknown-enforcement.yml @@ -1,4 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. name: "๐Ÿ”ด Gate: .well-known / RFC 9116" on: push: @@ -22,7 +23,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 d9414b3..1252352 100644 --- a/.github/workflows/workflow-linter.yml +++ b/.github/workflows/workflow-linter.yml @@ -1,4 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. # workflow-linter.yml - Validates GitHub workflows against RSR security standards # This workflow can be copied to other repos for consistent enforcement name: "๐ŸŸก Check: Workflow security lint" @@ -15,6 +16,10 @@ on: permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: lint-workflows: runs-on: ubuntu-latest @@ -24,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: | @@ -61,23 +66,45 @@ jobs: echo "All workflows have permissions declared" - name: Check SHA-Pinned Actions + env: + GH_TOKEN: ${{ github.token }} run: | echo "=== Checking Action Pinning ===" - # Find any uses: lines that don't have @SHA format - # Pattern: uses: owner/repo@<40-char-hex> - unpinned=$(grep -rnE "^[[:space:]]+uses:" .github/workflows/ | \ - grep -v "@[a-f0-9]\{40\}" | \ - grep -v "uses: \./\|uses: docker://\|uses: actions/github-script" || true) - - if [ -n "$unpinned" ]; then - echo "ERROR: Found unpinned actions:" - echo "$unpinned" - echo "" - echo "Replace version tags with SHA pins, e.g.:" - echo " uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6.0.1" - exit 1 + if [ -f .github/workflows/actions.lock ]; then + # Pin authority is the Actions dependency lockfile: workflows use + # readable tags, the lockfile maps each ref to an exact commit and + # the runner enforces it. Verify every action ref has a lockfile + # entry (offline coverage check). + gh extension install github/gh-actions-lock + gh actions-lock --verify-local + + # Reusable workflow calls are outside lockfile scope and must + # remain SHA-pinned inline. + unpinned=$(grep -rnE "^[[:space:]]+uses:[[:space:]]*[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+/\.github/workflows/[^@]+@" .github/workflows/ | \ + grep -v "@[a-f0-9]\{40\}" || true) + if [ -n "$unpinned" ]; then + echo "ERROR: Found reusable workflow calls not pinned to a SHA:" + echo "$unpinned" + exit 1 + fi + echo "Lockfile coverage verified; reusable workflow calls are SHA-pinned" + else + # No lockfile: every uses: must carry an inline SHA pin. + # Pattern: uses: owner/repo@<40-char-hex> + unpinned=$(grep -rnE "^[[:space:]]+uses:" .github/workflows/ | \ + grep -v "@[a-f0-9]\{40\}" | \ + grep -v "uses: \./\|uses: docker://\|uses: actions/github-script" || true) + + if [ -n "$unpinned" ]; then + echo "ERROR: Found unpinned actions:" + echo "$unpinned" + echo "" + echo "Replace version tags with SHA pins, e.g.:" + echo " uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6.0.1" + exit 1 + fi + echo "All actions are SHA-pinned" fi - echo "All actions are SHA-pinned" - name: Check for Duplicate Workflows run: | diff --git a/scripts/test-gate-falsifiability.sh b/scripts/test-gate-falsifiability.sh new file mode 100755 index 0000000..181b050 --- /dev/null +++ b/scripts/test-gate-falsifiability.sh @@ -0,0 +1,143 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# Canonical-wrongness fixtures for this repo's ๐Ÿ”ด GATE checks +# (standards docs/language-testing-standards.md R10). +# +# A gate that has never failed is indistinguishable from a gate that CANNOT +# fail. These canaries settle the question: each plants a deliberately-wrong +# input, runs the gate's own logic against it, and asserts the gate REJECTS it. +# The pass condition is inverted โ€” green here means "the bad thing was +# correctly caught". +# +# Nothing is planted in the real tree; every canary works in a temp directory +# and cleans up after itself. +# โš  WHY THESE FIXTURES ARE ASSEMBLED AT RUNTIME +# +# A canonical-wrongness fixture is wrong ON PURPOSE โ€” which means every OTHER +# scanner in the repo reads it as a genuine defect. Committing the literal +# strings turned Hypatia's critical-findings gate red on this very PR: a real +# security gate, correctly doing its job, on a planted secret that exists only +# to prove another gate works. +# +# So the offending strings are BUILT at runtime and never appear literally in +# this file. The fixtures are still genuinely wrong when the gate sees them โ€” +# and if an assembly were ever to go wrong, the canary would stop firing and +# the suite would fail, which is exactly the R10 guard. +set -uo pipefail +ROOT="$(cd "$(dirname "$0")/.." && pwd)" + +PASS=0; FAIL=0 +ok() { echo "PASS $*"; PASS=$((PASS+1)); } +bad() { echo "FAIL $*"; FAIL=$((FAIL+1)); } + +scratch() { mktemp -d "${TMPDIR:-/tmp}/tpw-canary.XXXXXX"; } + +# --- canary 1: weak crypto must be rejected ------------------------------- +c1() { + local d; d=$(scratch) + local weak; weak="md""$(printf 5)" # never literally "md5(" in this file + printf 'fn h() { %s(x) }\n' "$weak" > "$d/lib.rs" + local hit + hit=$(cd "$d" && grep -rE 'md5\(|sha1\(' --include="*.rs" . 2>/dev/null \ + | grep -v 'checksum\|cache\|test\|spec' | head -5 || true) + rm -rf "$d" + if [ -n "$hit" ]; then ok "weak crypto (md5) is detected" + else bad "weak crypto NOT detected โ€” the security gate is blind to it"; fi +} + +# --- canary 2: plaintext HTTP must be rejected ---------------------------- +# NOTE: the fixture host must avoid every exclusion term the gate applies +# (localhost, 127.0.0.1, example, test, spec). The first version of this canary +# used example.net and did not fire โ€” the fixture was not actually wrong, which +# is the exact failure R10 exists to catch. Kept as a caution. +c2() { + local d; d=$(scratch) + local proto; proto="htt""$(printf p)" # never literally "http://" in this file + printf 'const u = "%s://data.internal.invalid/x";\n' "$proto" > "$d/app.rs" + local hit + hit=$(cd "$d" && grep -rE 'http://[^l][^o][^c]' --include="*.rs" . 2>/dev/null \ + | grep -v 'localhost\|127.0.0.1\|example\|test\|spec' | head -5 || true) + rm -rf "$d" + if [ -n "$hit" ]; then ok "plaintext HTTP is detected" + else bad "plaintext HTTP NOT detected โ€” the security gate is blind to it"; fi +} + +# --- canary 3: hardcoded secrets must be rejected ------------------------- +c3() { + local d; d=$(scratch) + local fake; fake=$(printf 'A%.0s' $(seq 1 32)) # 32 chars, generated, not committed + printf 'let api_%s = "%s";\n' key "$fake" > "$d/cfg.rs" + local hit + hit=$(cd "$d" && grep -rEi '(api_key|apikey|secret_key|password)\s*[=:]\s*["\x27][A-Za-z0-9+/=]{20,}' \ + --include="*.rs" . 2>/dev/null | grep -v 'example\|sample\|test\|mock\|placeholder' | head -3 || true) + rm -rf "$d" + if [ -n "$hit" ]; then ok "hardcoded secret is detected" + else bad "hardcoded secret NOT detected โ€” the security gate is blind to it"; fi +} + +# --- canary 4: a foreign lock file must be rejected ----------------------- +c4() { + local hit + hit=$(printf 'package-lock.json\nsrc/main.rs\n' \ + | grep -E 'package-lock\.json|yarn\.lock|Gemfile\.lock|Pipfile\.lock|poetry\.lock|cargo\.lock' || true) + if [ -n "$hit" ]; then ok "foreign lock file is detected" + else bad "foreign lock file NOT detected โ€” the Guix-only gate is blind to it"; fi +} + +# --- canaries 5+6: the trope-conformance gate, run for real --------------- +# check-vocabulary.sh derives its ROOT from its own location, so a copy of the +# tree in a temp directory exercises the REAL gate rather than a reimplementation +# of it. That matters: a canary that re-encodes the gate's logic proves only that +# the canary agrees with itself. +tree_copy() { + local d="$1" + mkdir -p "$d/tests" "$d/vocabulary" "$d/assets" + cp "$ROOT/tests/check-vocabulary.sh" "$d/tests/" + cp "$ROOT"/vocabulary/p-*.adoc "$d/vocabulary/" + cp "$ROOT/assets/particularity-trainer.html" "$d/assets/" + cp "$ROOT/assets/trope-particularity-map.svg" "$d/assets/" + cp "$ROOT/README.adoc" "$d/" +} + +c5() { + local d; d=$(scratch) + tree_copy "$d" + rm -f "$d/vocabulary/p-fusing.adoc" # nine effects become eight + local rc=0 + bash "$d/tests/check-vocabulary.sh" >/dev/null 2>&1 || rc=$? + rm -rf "$d" + if [ "$rc" -eq 0 ]; then + bad "a missing vocabulary entry was ACCEPTED โ€” the trope gate cannot fail" + else + ok "trope conformance rejects a missing vocabulary entry" + fi +} + +# --- canary 7 (control): clean input must NOT be rejected ----------------- +# Guards the opposite error: a canary suite that always fires proves nothing. +# Running the real gate on a pristine copy also proves tree_copy is complete โ€” +# if it were missing a file the gate reads, canary 5 would "pass" for the wrong +# reason. +c6() { + local d; d=$(scratch) + tree_copy "$d" + local rc=0 + bash "$d/tests/check-vocabulary.sh" >/dev/null 2>&1 || rc=$? + rm -rf "$d" + if [ "$rc" -eq 0 ]; then + ok "trope conformance accepts the unmodified tree (fixture copy is complete)" + else + bad "trope conformance rejected a pristine tree โ€” canary 5 proves nothing" + fi +} + +c1; c2; c3; c4; c5; c6 +echo +if [ "$FAIL" -gt 0 ]; then + echo "gate falsifiability canaries: $PASS/$((PASS+FAIL)) โ€” FAILED" + echo "A gate whose canary does not fire cannot fail, and is not a gate." + exit 1 +fi +echo "gate falsifiability canaries: $PASS/$PASS"