From e36dafb8a97fd08ddecbd38837b547c87fb8086d Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Nov 2026 21:26:49 +0000 Subject: [PATCH] ci: 3 self-contained workflows, after the estate template hypatia-scan.yml, rust-ci.yml and scorecard.yml called reusable workflows of the standards repository. The Actions rule of this repository refuses a reusable workflow of a different owner, so each run failed with no job. Each file is now the workflow of the estate template (hyperpolymath/rsr-template-repo at 0dfde85), with step-level actions that are pinned to full commit SHAs. The text outside comments is the template text, except 1 step name in hypatia-scan.yml (no verdict grid in this repository). - hypatia-scan.yml: builds Hypatia at the pinned commit, uploads SARIF, fails on a critical finding. It uses the job token only, so the secret HYPATIA_SCAN_PAT is no longer passed. - rust-ci.yml: compiles scripts/*.rs and runs cargo when Cargo.toml exists. This repository has no Rust file today. - scorecard.yml: same schedule and dispatch triggers as before. - README.adoc (workflows): new section on these 3 files. governance.yml and mirror.yml stay callers: their template forms do less than the reusable workflows, and the owner decides on them. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_0136P7ZmiFSCoB3UR9Kk56rK --- .github/workflows/README.adoc | 12 +++ .github/workflows/hypatia-scan.yml | 146 ++++++++++++++++++++++++++--- .github/workflows/rust-ci.yml | 46 +++++++-- .github/workflows/scorecard.yml | 30 +++++- 4 files changed, 214 insertions(+), 20 deletions(-) diff --git a/.github/workflows/README.adoc b/.github/workflows/README.adoc index de681fc..92e39f1 100644 --- a/.github/workflows/README.adoc +++ b/.github/workflows/README.adoc @@ -23,6 +23,18 @@ either loses real function — keep both. Rust-secrets check and a shell-secrets check). An inline TruffleHog job was removed earlier as redundant with gitleaks; do not add it again. +== Three workflows are self-contained, not reusable callers + +`hypatia-scan.yml`, `rust-ci.yml` and `scorecard.yml` follow the workflows of +the estate template (`hyperpolymath/rsr-template-repo`). Earlier each one +called a reusable workflow of the standards repository. Those calls did not +start: the Actions rule of this repository refuses a reusable workflow of a +different owner, and each run failed with no job. Each `uses:` in these three +files is now a step-level action, pinned to a full commit SHA. + +`governance.yml` and `mirror.yml` are still callers of the standards +repository. They start only when the Actions rule permits that repository. + == SAST tools are complementary, not redundant * `codeql.yml` — CodeQL (matrix defaults to `actions`; every repo has workflows). diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index 4d7d324..6fdc489 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -1,17 +1,20 @@ # SPDX-License-Identifier: MPL-2.0 # -# Standalone Hypatia security scan (push / PR / weekly). This is NOT a duplicate -# of the `hypatia-scan` job in `static-analysis-gate.yml`: that job exists to -# feed the gitbot-fleet LEARNING pipeline (via `deposit-findings`), whereas this -# workflow is the repo's independent security scan. Same tool, two consumers. +# Self-contained Hypatia scan with SARIF upload (category `hypatia`), after +# `hypatia-scan.yml` of the estate template (hyperpolymath/rsr-template-repo). +# Hypatia is built from hyperpolymath/hypatia at the commit in HYPATIA_SHA. +# +# The earlier form of this file called the reusable workflow `hypatia-scan-reusable.yml` of the +# standards repository. That call did not start: each run failed with no job. +# +# `static-analysis-gate.yml` also runs Hypatia, for the gitbot-fleet deposit. # See .github/workflows/README.adoc. name: Hypatia Security Scan on: + pull_request: push: branches: [main, master, develop] - pull_request: - branches: [main, master] schedule: - cron: '0 0 * * 0' workflow_dispatch: @@ -19,15 +22,132 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true + permissions: - actions: read contents: read - security-events: write jobs: hypatia: - uses: metadatastician/standards/.github/workflows/hypatia-scan-reusable.yml@740c9d7d4ed4d1f319a04c92b28218beb75668d7 - # Only the secret that the called workflow declares. `secrets: inherit` - # gives every secret of this repository to the called workflow. - secrets: - HYPATIA_SCAN_PAT: ${{ secrets.HYPATIA_SCAN_PAT }} \ No newline at end of file + name: Hypatia Neurosymbolic Analysis + runs-on: ubuntu-24.04 + timeout-minutes: 20 + permissions: + contents: read + security-events: write + vulnerability-alerts: read # Dependabot alerts, for Hypatia's DA rules + env: + HYPATIA_SHA: 'be98f74ce93df200752f3798efa1305bfa3e0e10' + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Setup Elixir 1.19.4 / OTP 28.5.0.8 for Hypatia scanner + uses: erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124 # v1.24.1 + with: + elixir-version: '1.19.4' + otp-version: '28.5.0.8' + + - name: Cache pinned Hypatia build + id: cache-hypatia + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: | + ~/hypatia + ~/.hex + ~/.mix + key: hypatia-sarif-${{ runner.os }}-otp28.5.0.8-elx1194-${{ env.HYPATIA_SHA }} + + - name: Build pinned Hypatia scanner + run: | + set -euo pipefail + if [ ! -x "$HOME/hypatia/hypatia" ] || [ ! -x "$HOME/hypatia/hypatia-cli.sh" ]; then + rm -rf "$HOME/hypatia" + git init "$HOME/hypatia" + git -C "$HOME/hypatia" remote add origin https://github.com/hyperpolymath/hypatia.git + git -C "$HOME/hypatia" fetch --depth=1 origin "$HYPATIA_SHA" + git -C "$HOME/hypatia" checkout --detach FETCH_HEAD + ( + cd "$HOME/hypatia" + mix local.hex --force + mix local.rebar --force + mix deps.get + MIX_NO_PUBSUB=1 mix escript.build + ) + chmod +x "$HOME/hypatia/hypatia" "$HOME/hypatia/hypatia-cli.sh" + fi + + - name: Run Hypatia JSON and SARIF analysis + id: scan + env: + # The API-backed rules (code-scanning, Dependabot, secret-scanning, + # branch-protection, baseline-health) return no findings without a + # token. Scoped to this step. The job token reaches code-scanning + # (security-events) and Dependabot (vulnerability-alerts) alerts; + # it cannot read secret-scanning alerts, which need an App or PAT. + GITHUB_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + HYPATIA_FORMAT=json "$HOME/hypatia/hypatia-cli.sh" scan . --exit-zero > hypatia-findings.json + jq -e 'type == "array"' hypatia-findings.json >/dev/null + + HYPATIA_FORMAT=sarif "$HOME/hypatia/hypatia-cli.sh" scan . --exit-zero > hypatia.sarif + jq -e '.runs | type == "array" and length > 0' hypatia.sarif >/dev/null + + # Repo-relative paths, so that a reader can compare a finding with + # the output of `git diff --name-only`. + jq --arg ws "$GITHUB_WORKSPACE/" ' + map(if (.file | type) == "string" then + .file |= (if startswith($ws) then .[($ws | length):] else . end) + else . end)' hypatia-findings.json > hypatia-findings.relativized.json + + CRITICAL="$(jq '[.[] | select(.severity == "critical")] | length' hypatia-findings.json)" + HIGH="$(jq '[.[] | select(.severity == "high")] | length' hypatia-findings.json)" + TOTAL="$(jq 'length' hypatia-findings.json)" + { + echo "critical=$CRITICAL" + echo "high=$HIGH" + echo "total=$TOTAL" + } >> "$GITHUB_OUTPUT" + + # Uploaded even when the scan step failed: a missing artifact and a failed + # scan are different facts. `if-no-files-found: warn` because a scan that + # never produced a file is already visible as a red step here. + - name: Upload Hypatia findings + if: ${{ always() }} + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: hypatia-scan-findings + path: | + hypatia-findings.json + hypatia-findings.relativized.json + if-no-files-found: warn + retention-days: 90 + + - name: Upload Hypatia SARIF to GitHub Code Scanning + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 + with: + sarif_file: hypatia.sarif + category: hypatia + + - name: Enforce zero critical Hypatia findings + env: + CRITICAL_COUNT: ${{ steps.scan.outputs.critical }} + HIGH_COUNT: ${{ steps.scan.outputs.high }} + TOTAL_COUNT: ${{ steps.scan.outputs.total }} + run: | + set -euo pipefail + { + echo "## Hypatia Neurosymbolic Scan" + echo "" + echo "- Critical: ${CRITICAL_COUNT}" + echo "- High: ${HIGH_COUNT}" + echo "- Total findings: ${TOTAL_COUNT}" + } >> "$GITHUB_STEP_SUMMARY" + if [ "$CRITICAL_COUNT" != "0" ]; then + echo "::error::Hypatia reported ${CRITICAL_COUNT} critical finding(s)" + exit 1 + fi diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 8993259..8dd4ba4 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -1,21 +1,55 @@ # SPDX-License-Identifier: MPL-2.0 -# Rust CI — thin wrapper calling the shared estate reusable in -# hyperpolymath/standards. Configure once, propagate everywhere. -# See: docs/CI-REUSABLE-WORKFLOWS.adoc in standards. +# +# Rust CI, after `rust-ci.yml` of the estate template +# (hyperpolymath/rsr-template-repo): compiles standalone scripts/*.rs utilities +# with -D warnings and runs cargo check and cargo test when a Cargo.toml is +# present. This repository has no Rust file today. +# +# The earlier form of this file called the reusable workflow `rust-ci-reusable.yml` of the +# standards repository. That call did not start: each run failed with no job. name: Rust CI + on: push: branches: [main, master] pull_request: + workflow_dispatch: + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true + permissions: contents: read + jobs: rust-ci: - uses: metadatastician/standards/.github/workflows/rust-ci-reusable.yml@571cc734cd69fb846032ec77a662aa8ee4fc32cd + name: Rust standalone tools and workspace gate + runs-on: ubuntu-24.04 + timeout-minutes: 15 permissions: - actions: read contents: read - security-events: write + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Compile standalone Rust helpers and run tests + run: | + set -euo pipefail + rustc --version + mkdir -p "$RUNNER_TEMP/rust-bins" + for src in scripts/*.rs; do + [ -f "$src" ] || continue + bin_name="$(basename "$src" .rs)" + echo "Compiling $src (-D warnings)..." + rustc --edition 2021 -D warnings "$src" -o "$RUNNER_TEMP/rust-bins/$bin_name" + done + if [ -f tests/workflows/archetype_lock_merge_test.sh ]; then + bash tests/workflows/archetype_lock_merge_test.sh + fi + if [ -f Cargo.toml ]; then + cargo check --all-targets --locked + cargo test --locked + fi diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 8d987f7..264cfd1 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -1,4 +1,10 @@ # SPDX-License-Identifier: MPL-2.0 +# +# OSSF Scorecard, after `scorecard.yml` of the estate template +# (hyperpolymath/rsr-template-repo). It runs on a schedule and on manual dispatch. +# +# The earlier form of this file called the reusable workflow `scorecard-reusable.yml` of the +# standards repository. That call did not start: each run failed with no job. name: OSSF Scorecard on: @@ -9,14 +15,36 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true + permissions: contents: read jobs: scorecard: - uses: metadatastician/standards/.github/workflows/scorecard-reusable.yml@d7c22711e830e1f383846472f6e9b99debdb201e + if: github.event_name != 'pull_request' + name: OSSF Scorecard analysis + runs-on: ubuntu-24.04 + timeout-minutes: 15 permissions: actions: read contents: read security-events: write id-token: write + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Run OSSF Scorecard + uses: ossf/scorecard-action@fa7117db83c52d787ae5a89f350999701505d78e # v2.5.0 + with: + results_file: results.sarif + results_format: sarif + publish_results: ${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }} + + - name: Upload Scorecard SARIF to GitHub Code Scanning + uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 + with: + sarif_file: results.sarif + category: scorecard