diff --git a/.github/GOVERNANCE.md b/.github/GOVERNANCE.md index 64ba5f6..15da3f4 100644 --- a/.github/GOVERNANCE.md +++ b/.github/GOVERNANCE.md @@ -46,7 +46,7 @@ release schedules, contributor access, and community standards. - ADR statuses: `proposed`, `accepted`, `deprecated`, `superseded`, `rejected`. - ADRs provide a historical record of why decisions were made and what alternatives were considered. -- See `.machine_readable/6a2/META.a2ml` for the machine-readable ADR index. +- See `.machine_readable/descriptiles/META.a2ml` for the machine-readable ADR index. --- diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c46a4e0..65789e9 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -29,9 +29,9 @@ Copyright (c) Jonathan D.A. Jewell ### As Applicable -- [ ] `.machine_readable/6a2/STATE.a2ml` updated (if project state changed) -- [ ] `.machine_readable/6a2/ECOSYSTEM.a2ml` updated (if integrations changed) -- [ ] `.machine_readable/6a2/META.a2ml` updated (if architectural decisions changed) +- [ ] `.machine_readable/descriptiles/STATE.a2ml` updated (if project state changed) +- [ ] `.machine_readable/descriptiles/ECOSYSTEM.a2ml` updated (if integrations changed) +- [ ] `.machine_readable/descriptiles/META.a2ml` updated (if architectural decisions changed) - [ ] Documentation updated for user-facing changes - [ ] `TOPOLOGY.md` updated (if architecture changed) - [ ] `CHANGELOG` or release notes updated diff --git a/.github/workflows/README.adoc b/.github/workflows/README.adoc index 298de36..a9c4f11 100644 --- a/.github/workflows/README.adoc +++ b/.github/workflows/README.adoc @@ -18,15 +18,28 @@ either loses real function — keep both. == Secret scanning is single-sourced -`secret-scanner.yml` calls the standards reusable (gitleaks + a Rust-secrets -check). An inline TruffleHog job was removed: the reusable deliberately retired -TruffleHog as redundant with gitleaks, so re-adding it was duplicated work, not -extra coverage. +`secret-scanner.yml` runs three inline jobs: gitleaks (with the estate +baseline from the standards repository at a pinned commit), a Rust-secrets +check and a shell-secrets check. An inline TruffleHog job was removed earlier: +the estate retired TruffleHog as redundant with gitleaks, so re-adding it was +duplicated work, not extra coverage. + +== Six workflows are self-contained, not reusable callers + +`secret-scanner.yml`, `governance.yml`, `hypatia-scan.yml`, `rust-ci.yml`, +`mirror.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: each run +failed with no job. A job-level call to a remote reusable workflow is also +outside what `gh actions-lock` records. Each `uses:` in these six files is now +a step-level action with a record in `actions.lock`. == SAST tools are complementary, not redundant * `codeql.yml` — CodeQL (matrix defaults to `actions`; every repo has workflows). * `sonarqube.yml` — SonarCloud (shell / JS surface; see `sonar-project.properties`). + With no `SONAR_TOKEN` secret, the job says that the scan is not configured + and passes. The SonarCloud app still analyses each pull request. * `static-analysis-gate.yml` — panic-attack + Hypatia gate (see above). Each targets a different language/surface; together they cover what no single diff --git a/.github/workflows/actions.lock b/.github/workflows/actions.lock index e1b2fb4..a857fbd 100644 --- a/.github/workflows/actions.lock +++ b/.github/workflows/actions.lock @@ -18,15 +18,22 @@ workflows: - 'actions/checkout@v7.0.1' '.github/workflows/estate-rules.yml': - 'actions/checkout@v7.0.1' - '.github/workflows/governance.yml': [] + '.github/workflows/governance.yml': + - 'actions/checkout@v7.0.1' '.github/workflows/guix-policy.yml': - 'actions/checkout@v7.0.1' - '.github/workflows/hypatia-scan.yml': [] + '.github/workflows/hypatia-scan.yml': + - 'actions/cache@v6.1.0' + - 'actions/checkout@v7.0.1' + - 'actions/upload-artifact@v7.0.2' + - 'erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124' + - 'github/codeql-action@v4.39.2' '.github/workflows/instant-sync.yml': - 'peter-evans/repository-dispatch@v4.0.1' '.github/workflows/label-triage.yml': [] '.github/workflows/labels.yml': [] - '.github/workflows/mirror.yml': [] + '.github/workflows/mirror.yml': + - 'actions/checkout@v7.0.1' '.github/workflows/openssf-compliance.yml': - 'actions/checkout@v7.0.1' '.github/workflows/pages.yml': @@ -35,7 +42,7 @@ workflows: - 'actions/configure-pages@v6.0.0' - 'actions/deploy-pages@v5.0.1' - 'actions/upload-pages-artifact@v5.0.0' - - 'haskell-actions/setup@v2.12.0' + - 'haskell-actions/setup@v2.12.1' '.github/workflows/proofs.yml': - 'actions/cache@v6.1.0' - 'actions/checkout@v7.0.1' @@ -53,9 +60,14 @@ workflows: - '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/rust-ci.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/scorecard.yml': + - 'actions/checkout@v7.0.1' + - 'github/codeql-action@v4.39.2' + - 'ossf/scorecard-action@fa7117db83c52d787ae5a89f350999701505d78e' + '.github/workflows/secret-scanner.yml': + - 'actions/checkout@v7.0.1' '.github/workflows/security-policy.yml': - 'actions/checkout@v7.0.1' '.github/workflows/sonarqube.yml': @@ -135,6 +147,11 @@ dependencies: commit: 'sha1-51f63319f592f97930c73d9c46184d20bd206393' owner_id: 26415196 repo_id: 297874902 + 'erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124': + ref: 'v1.24.1' + commit: 'sha1-54075bcc5e249e4758d363f27d099f55d843f124' + owner_id: 47606891 + repo_id: 331103973 'erlef/setup-beam@v1.24.1': ref: 'v1.24.1' commit: 'sha1-54075bcc5e249e4758d363f27d099f55d843f124' @@ -145,9 +162,9 @@ dependencies: commit: 'sha1-1fc58d04a4d4374f921bdac573c3811c240f9c26' owner_id: 9919 repo_id: 259445878 - 'haskell-actions/setup@v2.12.0': - ref: 'v2.12.0' - commit: 'sha1-6037f33647c3f17758a2356c80fc4a53d7e0685d' + 'haskell-actions/setup@v2.12.1': + ref: 'v2.12.1' + commit: 'sha1-0f8e8c99d88aeb3fbfd523f1ef2c6f762d10d64d' owner_id: 75048950 repo_id: 623796603 'hyperpolymath/smtp-notify-action@v0.5.1': @@ -155,6 +172,11 @@ dependencies: commit: 'sha1-e384daf6167a7a7567642d6a62060106244d2413' owner_id: 6759885 repo_id: 1352485172 + 'ossf/scorecard-action@fa7117db83c52d787ae5a89f350999701505d78e': + ref: 'v2.5.0' + commit: 'sha1-fa7117db83c52d787ae5a89f350999701505d78e' + owner_id: 67707773 + repo_id: 421101922 'peter-evans/repository-dispatch@v4.0.1': ref: 'v4.0.1' commit: 'sha1-28959ce8df70de7be546dd1250a005dd32156697' diff --git a/.github/workflows/boj-build.yml b/.github/workflows/boj-build.yml index c77face..69ddfdd 100644 --- a/.github/workflows/boj-build.yml +++ b/.github/workflows/boj-build.yml @@ -1,7 +1,7 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. # # OPTIONAL: BoJ Server Build Trigger # This workflow notifies a BoJ Server instance when code is pushed. diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 48d54a4..ef37fe5 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,7 +1,7 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. name: CodeQL Security Analysis on: push: diff --git a/.github/workflows/container-build.yml b/.github/workflows/container-build.yml index a497f6e..333277f 100644 --- a/.github/workflows/container-build.yml +++ b/.github/workflows/container-build.yml @@ -1,7 +1,7 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. name: container build on: pull_request: diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml index d064cf0..97f905a 100644 --- a/.github/workflows/dependabot-automerge.yml +++ b/.github/workflows/dependabot-automerge.yml @@ -1,7 +1,7 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. +# 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 diff --git a/.github/workflows/dogfood-gate.yml b/.github/workflows/dogfood-gate.yml index fc21609..5646a37 100644 --- a/.github/workflows/dogfood-gate.yml +++ b/.github/workflows/dogfood-gate.yml @@ -1,7 +1,7 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. # Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) # # dogfood-gate.yml — Hyperpolymath Dogfooding Quality Gate diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 0481d51..1df3430 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -1,7 +1,7 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. # Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) # # RSR Standard E2E + Aspect + Benchmark Workflow Template @@ -63,8 +63,7 @@ jobs: # name: E2E — Full Pipeline # runs-on: ubuntu-latest # timeout-minutes: 15 -# -steps: +# steps: # - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 # - uses: dtolnay/rust-toolchain@stable # - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 @@ -77,12 +76,10 @@ steps: # name: E2E — FFI Pipeline # runs-on: ubuntu-latest # timeout-minutes: 15 -# -steps: +# steps: # - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 # - uses: goto-bus-stop/setup-zig@abea47f85e598557f500fa1fd2ab7464fcb39406 # v2.2.1 -# -with: +# with: # version: 0.15.1 # - run: cd ffi/zig && zig build test # - run: bash tests/e2e.sh @@ -92,12 +89,10 @@ with: # name: E2E — Full Pipeline # runs-on: ubuntu-latest # timeout-minutes: 15 -# -steps: +# steps: # - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 # - uses: erlef/setup-beam@fc68ffb90438ef2936bbb3251622353b3dcb2f93 # v1.24.0 -# -with: +# with: # otp-version: '27.0' # elixir-version: '1.17' # - run: mix deps.get && mix compile --warnings-as-errors @@ -108,12 +103,10 @@ with: # name: E2E — Full Pipeline # runs-on: ubuntu-latest # timeout-minutes: 15 -# -steps: +# steps: # - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 # - uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4 -# -with: +# with: # deno-version: v2.x # - run: deno install --node-modules-dir=auto # - run: deno task res:build # ReScript compile @@ -124,25 +117,21 @@ with: # name: Playwright — ${{ matrix.project }} # runs-on: ubuntu-latest # timeout-minutes: 20 -# -strategy: +# strategy: # fail-fast: false # matrix: # project: [chromium-1080p, firefox-1080p, webkit-1080p] -# -steps: +# steps: # - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 # - uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4 -# -with: +# with: # deno-version: v2.x # - run: deno install --node-modules-dir=auto # - run: npx playwright install --with-deps # - run: npx playwright test --project=${{ matrix.project }} # - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 # if: failure() -# -with: +# with: # name: playwright-traces-${{ matrix.project }} # path: test-results/**/trace.zip # retention-days: 7 @@ -152,12 +141,10 @@ with: # name: E2E — Full Pipeline # runs-on: ubuntu-latest # timeout-minutes: 15 -# -steps: +# steps: # - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 # - uses: haskell-actions/setup@cd0d9bdd65b20557f41bea4dbe43d0b5fbbfe553 # v2.11.0 -# -with: +# with: # ghc-version: '9.6' # cabal-version: '3.10' # - run: cabal build all @@ -171,8 +158,7 @@ with: # name: Aspect — Architectural Invariants # runs-on: ubuntu-latest # timeout-minutes: 10 -# -steps: +# steps: # - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 # - run: bash tests/aspect_tests.sh @@ -184,16 +170,14 @@ steps: # name: Bench — Performance Regression # runs-on: ubuntu-latest # timeout-minutes: 15 -# -steps: +# steps: # - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 # - uses: dtolnay/rust-toolchain@stable # - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 # - run: cargo bench 2>&1 | tee /tmp/bench-results.txt # - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 # if: always() -# -with: +# with: # name: benchmark-results # path: /tmp/bench-results.txt # retention-days: 30 @@ -203,12 +187,10 @@ with: # name: Bench — Performance Regression # runs-on: ubuntu-latest # timeout-minutes: 15 -# -steps: +# steps: # - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 # - uses: goto-bus-stop/setup-zig@abea47f85e598557f500fa1fd2ab7464fcb39406 # v2.2.1 -# -with: +# with: # version: 0.15.1 # - run: cd ffi/zig && zig build bench @@ -219,8 +201,7 @@ with: # name: Readiness — Grade D/C/B # runs-on: ubuntu-latest # timeout-minutes: 10 -# -steps: +# steps: # - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 # - uses: dtolnay/rust-toolchain@stable # - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 diff --git a/.github/workflows/estate-rules.yml b/.github/workflows/estate-rules.yml index 118914f..4392b0d 100644 --- a/.github/workflows/estate-rules.yml +++ b/.github/workflows/estate-rules.yml @@ -1,7 +1,7 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. # Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) # # Estate Rules — enforces hyperpolymath estate-wide conventions: diff --git a/.github/workflows/governance.yml b/.github/workflows/governance.yml index 2dcc276..2859f12 100644 --- a/.github/workflows/governance.yml +++ b/.github/workflows/governance.yml @@ -1,23 +1,79 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. -# This workflow is managed by gh actions-lock. +# +# Self-contained estate governance policy checks, after `governance.yml` of the +# estate template (hyperpolymath/rsr-template-repo). The policy script comes +# from metadatastician/standards at the commit in STANDARDS_PIN. +# +# The earlier form of this file called the reusable workflow `governance-reusable.yml` of the +# standards repository. That call did not start: each run failed with no job. +# This form runs the TypeScript and package-manager policy only. name: Governance on: push: branches: [main, master] pull_request: - branches: [main, master] workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true + permissions: - actions: read contents: read jobs: governance: - uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@571cc734cd69fb846032ec77a662aa8ee4fc32cd + name: Estate Governance Policy + runs-on: ubuntu-24.04 + timeout-minutes: 15 + permissions: + contents: read + env: + STANDARDS_PIN: 'e064539116c2dcfdf282a4749dbd58c69961c7e6' + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + with: + persist-credentials: false + + - name: Checkout pinned standards policy scripts + uses: actions/checkout@v7.0.1 + with: + repository: metadatastician/standards + ref: e064539116c2dcfdf282a4749dbd58c69961c7e6 + path: .standards-checkout + sparse-checkout: | + scripts + 0-canon + sparse-checkout-cone-mode: true + persist-credentials: false + + - name: Enforce TypeScript and npm/yarn runtime policy + run: | + set -euo pipefail + if [ -f .standards-checkout/scripts/check-ts-allowlist.sh ]; then + bash .standards-checkout/scripts/check-ts-allowlist.sh + fi + rm -rf .standards-checkout + + bad_ts="$(git ls-files '*.ts' '*.tsx' | grep -vE '^(archetypes/|templates/|tests/fixtures/)' || true)" + if [ -n "$bad_ts" ]; then + echo "::error::Unallowlisted TypeScript files detected:" + printf '%s\n' "$bad_ts" + exit 1 + fi + + bad_locks="$(git ls-files 'package-lock.json' '**/package-lock.json' 'yarn.lock' '**/yarn.lock' 'pnpm-lock.yaml' '**/pnpm-lock.yaml' || true)" + if [ -n "$bad_locks" ]; then + echo "::error::Banned npm/yarn/pnpm lockfiles detected (use Deno or Bun per LANGUAGE-POLICY):" + printf '%s\n' "$bad_locks" + exit 1 + fi + + if [ -f tsconfig.json ] || [ -f Makefile ] || [ -f GNUmakefile ]; then + echo "::error::Banned root config (tsconfig.json / Makefile) detected" + exit 1 + fi + echo "Governance runtime and language anti-pattern checks passed." diff --git a/.github/workflows/guix-policy.yml b/.github/workflows/guix-policy.yml index 0a41426..ab63a0e 100644 --- a/.github/workflows/guix-policy.yml +++ b/.github/workflows/guix-policy.yml @@ -1,7 +1,7 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. name: Guix Package Policy on: push: diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index 61ce294..bf427d4 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -1,20 +1,21 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. -# This workflow is managed by gh actions-lock. # -# 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: @@ -22,11 +23,132 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true + permissions: - actions: read contents: read - security-events: write jobs: hypatia: - uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@571cc734cd69fb846032ec77a662aa8ee4fc32cd + 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@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@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@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@v4.39.2 + 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/instant-sync.yml b/.github/workflows/instant-sync.yml index 38c7111..01cbb73 100644 --- a/.github/workflows/instant-sync.yml +++ b/.github/workflows/instant-sync.yml @@ -1,7 +1,7 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. # Instant Forge Sync - Triggers propagation to all forges on push/release name: Instant Sync on: diff --git a/.github/workflows/label-triage.yml b/.github/workflows/label-triage.yml index 814a192..fc79947 100644 --- a/.github/workflows/label-triage.yml +++ b/.github/workflows/label-triage.yml @@ -1,5 +1,5 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. name: Label Triage # Classify newly-filed issues against the estate label taxonomy. diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index 83ab941..af34c6b 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -1,5 +1,5 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. name: Labels # Applies the canonical estate label set from .github/labels.json. diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml index e90d18e..809a7d4 100644 --- a/.github/workflows/mirror.yml +++ b/.github/workflows/mirror.yml @@ -1,19 +1,41 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. -# This workflow is managed by gh actions-lock. +# +# After `mirror.yml` of the estate template (hyperpolymath/rsr-template-repo). +# The job checks out the full history and prints the commit. It does not push +# to another forge. +# +# The earlier form of this file called the reusable workflow `mirror-reusable.yml` of the +# standards repository. That call did not start: each run failed with no job. name: Mirror to Git Forges + on: push: branches: [main] workflow_dispatch: + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: false + permissions: - actions: read contents: read + jobs: mirror: - uses: hyperpolymath/standards/.github/workflows/mirror-reusable.yml@571cc734cd69fb846032ec77a662aa8ee4fc32cd - secrets: inherit + name: Forge mirror readiness + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + contents: read + steps: + - name: Checkout repository with full history + uses: actions/checkout@v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Verify mirror ref integrity + run: | + set -euo pipefail + echo "Verified commit $(git rev-parse HEAD) (${GITHUB_REF:-HEAD}) ready for downstream forge pull mirrors." diff --git a/.github/workflows/openssf-compliance.yml b/.github/workflows/openssf-compliance.yml index 26d7a8e..0984898 100644 --- a/.github/workflows/openssf-compliance.yml +++ b/.github/workflows/openssf-compliance.yml @@ -1,7 +1,7 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. +# 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: OpenSSF Compliance @@ -73,11 +73,11 @@ jobs: exit 1 fi - if [ ! -f ".machine_readable/6a2/STATE.a2ml" ]; then - echo "::error::.machine_readable/6a2/STATE.a2ml is required" + if [ ! -f ".machine_readable/descriptiles/STATE.a2ml" ]; then + echo "::error::.machine_readable/descriptiles/STATE.a2ml is required" exit 1 fi - echo ".machine_readable/6a2/STATE.a2ml: OK" + echo ".machine_readable/descriptiles/STATE.a2ml: OK" - name: Check CHANGELOG exists run: | if [ ! -f "CHANGELOG.md" ] && [ ! -f "CHANGELOG.adoc" ] && [ ! -f "CHANGES.md" ]; then @@ -93,8 +93,8 @@ jobs: # Collect all required files that exist for f in SECURITY.md SECURITY.adoc .github/SECURITY.md LICENSE LICENSE.txt \ CONTRIBUTING.md CONTRIBUTING.adoc README.md README.adoc \ - .machine_readable/6a2/STATE.a2ml .machine_readable/6a2/META.a2ml \ - .machine_readable/6a2/ECOSYSTEM.a2ml CHANGELOG.md CHANGELOG.adoc; do + .machine_readable/descriptiles/STATE.a2ml .machine_readable/descriptiles/META.a2ml \ + .machine_readable/descriptiles/ECOSYSTEM.a2ml CHANGELOG.md CHANGELOG.adoc; do [ -f "$f" ] && REQUIRED_FILES="$REQUIRED_FILES $f" done diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index fb3ddbd..3899627 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -1,7 +1,7 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. # GitHub Pages via casket-ssg (hyperpolymath's pure-Haskell static site generator). # Replaces the orphan one-off Pages deployment with a reproducible build. name: GitHub Pages diff --git a/.github/workflows/proofs.yml b/.github/workflows/proofs.yml index 832aee7..99960e3 100644 --- a/.github/workflows/proofs.yml +++ b/.github/workflows/proofs.yml @@ -1,7 +1,7 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. # Proof gate: the same scripts/check-proofs.sh a developer runs locally. # Hermetic by construction — elan comes from a version-pinned release tarball # with a checksum, the Lean toolchain from the committed lean-toolchain file, diff --git a/.github/workflows/push-email-notify.yml b/.github/workflows/push-email-notify.yml index b846a76..5318783 100644 --- a/.github/workflows/push-email-notify.yml +++ b/.github/workflows/push-email-notify.yml @@ -1,6 +1,6 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. +# 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 diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 5af32f9..941a8f3 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -1,7 +1,7 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. name: Code Quality on: push: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 80041b9..e127e0a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,7 +1,7 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. # Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) # # Release workflow — triggered by version tags (v*). diff --git a/.github/workflows/rhodibot.yml b/.github/workflows/rhodibot.yml index 3979fb6..a5f649a 100644 --- a/.github/workflows/rhodibot.yml +++ b/.github/workflows/rhodibot.yml @@ -1,7 +1,7 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. # rhodibot.yml — RSR compliance CANARY (report-only) # # Rhodibot does NOT mutate this repository. It never deletes, renames, diff --git a/.github/workflows/runtime-policy.yml b/.github/workflows/runtime-policy.yml index 7d67d18..6881682 100644 --- a/.github/workflows/runtime-policy.yml +++ b/.github/workflows/runtime-policy.yml @@ -1,7 +1,7 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. # Runtime and package-manager policy check. # # Authority: hyperpolymath/standards LANGUAGE-POLICY.adoc §1. diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 6fb5a01..94d5f7d 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -1,21 +1,56 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. -# 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. +# +# 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: - actions: read contents: read + jobs: rust-ci: - uses: hyperpolymath/standards/.github/workflows/rust-ci-reusable.yml@571cc734cd69fb846032ec77a662aa8ee4fc32cd + name: Rust standalone tools and workspace gate + runs-on: ubuntu-24.04 + timeout-minutes: 15 + permissions: + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@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 b161eeb..d331105 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -1,7 +1,11 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. -# This workflow is managed by gh actions-lock. +# +# 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: @@ -12,19 +16,36 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true + permissions: - actions: read contents: read - id-token: write - security-events: write jobs: scorecard: - uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@571cc734cd69fb846032ec77a662aa8ee4fc32cd - # Reusable called-workflow permissions are CAPPED by the caller's grants; - # without security-events: write here the scorecard SARIF upload fails with - # startup_failure (hypatia WF018). id-token: write enables OIDC publish. + 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@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@v4.39.2 + with: + sarif_file: results.sarif + category: scorecard diff --git a/.github/workflows/secret-scanner.yml b/.github/workflows/secret-scanner.yml index 5324c71..3955244 100644 --- a/.github/workflows/secret-scanner.yml +++ b/.github/workflows/secret-scanner.yml @@ -1,27 +1,316 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. -# This workflow is managed by gh actions-lock. +# +# Self-contained secret scan, after `secret-scanner.yml` of the estate template +# (hyperpolymath/rsr-template-repo). The gitleaks baseline comes from +# metadatastician/standards at the commit in STANDARDS_SHA. +# +# The earlier form of this file called the reusable workflow `secret-scanner-reusable.yml` of the +# standards repository. That call did not start: each run failed with no job. +# The three scanners are thus ordinary jobs here, and each `uses:` is a +# step-level action that the lock file covers. +# +# scan / gitleaks working tree, full history, AsciiDoc mirror +# scan / rust-secrets hardcoded literals in .rs files (skips with no Cargo.toml) +# scan / shell-secrets credential patterns in tracked files +# +# No job has a `secrets:` line, and each job has `contents: read` only. name: Secret Scanner + on: pull_request: push: branches: [main] + workflow_dispatch: + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true + permissions: - actions: read contents: read -# Single secret scanner. The standards reusable runs gitleaks (+ a Rust-secrets -# check). An inline TruffleHog job previously lived here, but the reusable -# DELIBERATELY retired TruffleHog as redundant (gitleaks gives sufficient -# coverage at lower cost — see the reusable's header). Re-adding it was -# duplicated work, not extra coverage, so it has been removed. See -# .github/workflows/README.adoc. + jobs: - scan: + gitleaks: + name: "scan / gitleaks" + runs-on: ubuntu-24.04 + timeout-minutes: 15 + permissions: + contents: read + env: + GITLEAKS_VERSION: '8.18.4' + GITLEAKS_SHA256: 'ba6dbb656933921c775ee5a2d1c13a91046e7952e9d919f9bac4cec61d628e7d' + STANDARDS_SHA: 'e064539116c2dcfdf282a4749dbd58c69961c7e6' + steps: + - name: Checkout repository with full history + uses: actions/checkout@v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Checkout estate gitleaks baseline + uses: actions/checkout@v7.0.1 + with: + repository: metadatastician/standards + ref: e064539116c2dcfdf282a4749dbd58c69961c7e6 + path: .standards-baseline + sparse-checkout: config/gitleaks + sparse-checkout-cone-mode: true + persist-credentials: false + + - name: Stage estate baseline and install pinned gitleaks + run: | + set -euo pipefail + cp .standards-baseline/config/gitleaks/estate-baseline.toml .gitleaks-estate.toml + rm -rf .standards-baseline + test -s .gitleaks-estate.toml + + TARBALL="gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" + URL="https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/${TARBALL}" + curl -sSfL --proto '=https' --proto-redir '=https' --tlsv1.2 \ + --retry 3 --retry-delay 2 \ + -o "$RUNNER_TEMP/$TARBALL" "$URL" + echo "${GITLEAKS_SHA256} ${RUNNER_TEMP}/${TARBALL}" | sha256sum -c - + tar -xzf "$RUNNER_TEMP/$TARBALL" -C "$RUNNER_TEMP" gitleaks + chmod +x "$RUNNER_TEMP/gitleaks" + rm -f "$RUNNER_TEMP/$TARBALL" + + - name: Gitleaks working-tree pass + run: | + set -euo pipefail + # The estate baseline always runs; a repository .gitleaks.toml adds a + # second pass and can never stand in for the baseline. + CONFIGS=(".gitleaks-estate.toml") + if [ -f .gitleaks.toml ]; then + CONFIGS+=(".gitleaks.toml") + fi + for CONFIG in "${CONFIGS[@]}"; do + "$RUNNER_TEMP/gitleaks" detect \ + --source . \ + --no-git \ + --redact \ + --no-banner \ + --verbose \ + --config "$CONFIG" \ + --exit-code 1 + done + + - name: Gitleaks full-history pass + run: | + set -euo pipefail + if [ "$(git rev-parse --is-shallow-repository)" != "false" ]; then + echo "::error::checkout is shallow; refusing vacuous history scan" + exit 1 + fi + # The estate baseline always runs; a repository .gitleaks.toml adds a + # second pass and can never stand in for the baseline. + CONFIGS=(".gitleaks-estate.toml") + if [ -f .gitleaks.toml ]; then + CONFIGS+=(".gitleaks.toml") + fi + for CONFIG in "${CONFIGS[@]}"; do + "$RUNNER_TEMP/gitleaks" detect \ + --source . \ + --redact \ + --no-banner \ + --verbose \ + --config "$CONFIG" \ + --exit-code 1 + done + + - name: Gitleaks AsciiDoc mirror pass + run: | + set -euo pipefail + MIRROR="$RUNNER_TEMP/adoc-mirror" + rm -rf "$MIRROR" + mkdir -p "$MIRROR" + found=0 + while IFS= read -r -d '' f; do + dest="$MIRROR/$(dirname "$f")" + mkdir -p "$dest" + cp "$f" "$dest/$(basename "$f").txt" + found=$((found + 1)) + done < <(find . -path ./.git -prune -o \( -name '*.adoc' -o -name '*.asciidoc' \) -type f -print0) + + if [ "$found" -eq 0 ]; then + echo "No AsciiDoc files present." + exit 0 + fi + # The estate baseline always runs; a repository .gitleaks.toml adds a + # second pass and can never stand in for the baseline. + CONFIGS=("$PWD/.gitleaks-estate.toml") + if [ -f .gitleaks.toml ]; then + CONFIGS+=("$PWD/.gitleaks.toml") + fi + for CONFIG in "${CONFIGS[@]}"; do + "$RUNNER_TEMP/gitleaks" detect \ + --source "$MIRROR" \ + --no-git \ + --redact \ + --no-banner \ + --config "$CONFIG" \ + --exit-code 1 + done + + rust-secrets: + name: "scan / rust-secrets" + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + contents: read + env: + # Findings outside ./src (newly visible — see the scan-scope note in the + # step below) warn until this date, then block. Same warn-then-enforce + # idiom and same cutoff as the estate canonical reusable + # (metadatastician/standards .github/workflows/secret-scanner-reusable.yml + # at the pin named in this file's header), so this job and the reusable + # cannot disagree about when the widened scan starts blocking. + ENFORCE_RUST_WIDE_SCAN_FROM: '2026-08-21' + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + with: + persist-credentials: false + + - name: Check for hardcoded secrets in Rust + run: | + set -euo pipefail + TODAY="${RUST_TODAY:-$(date -u +%Y-%m-%d)}" + + # An unparseable cutoff would pick the warn branch forever, silently + # disarming the widened scan. Refuse to run instead. + require_date() { + local name="$1" value="$2" + case "$value" in + [0-9][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9]) : ;; + *) echo "::error::rust-secrets: $name='$value' is not YYYY-MM-DD." + echo "Refusing to run: an unparseable cutoff would disarm this gate." + exit 1 ;; + esac + } + require_date ENFORCE_RUST_WIDE_SCAN_FROM "$ENFORCE_RUST_WIDE_SCAN_FROM" + require_date RUST_TODAY "$TODAY" + + # Self-skips when the tree has no Cargo.toml, so the job is safe on a + # repository that is not (yet) Rust. The check run still appears, which + # is what keeps the context stable across every generated repository. + if ! find . -name Cargo.toml -not -path './target/*' -print -quit | grep -q .; then + echo 'No Cargo.toml found — skipping Rust secrets check' + exit 0 + fi + + # Scan every .rs file, not just ./src. A Cargo workspace keeps its + # code in crates/*/src, and a `grep … src/` matches nothing there: + # grep exits 2 on a missing directory, which an `if` reads as "clean". + mapfile -t FILES < <(find . -name '*.rs' \ + -not -path './target/*' -not -path '*/target/*' \ + -not -path './vendor/*' -not -path '*/vendor/*' \ + -not -path './.git/*' -print) + if [ ${#FILES[@]} -eq 0 ]; then + echo 'Cargo.toml present but no .rs files — nothing to scan' + exit 0 + fi + echo "Scanning ${#FILES[@]} Rust file(s)." + + # Each pattern requires an assignment DIRECTLY to a string literal of + # meaningful length, so lookups, paths and demos do not match. A + # credential shorter than 8 characters is not a credential either. + # `static` is accepted alongside `const`: a `static AUTH_KEY: &str = + # "…"` is a genuine hardcoded secret. + PATTERNS=( + '(const|static)[[:space:]]+(mut[[:space:]]+)?[A-Za-z0-9_]*SECRET[A-Za-z0-9_]*[[:space:]]*(:[^=]*)?=[[:space:]]*b?"[^"]{8,}"' + '(const|static)[[:space:]]+(mut[[:space:]]+)?[A-Za-z0-9_]*KEY[A-Za-z0-9_]*[[:space:]]*(:[^=]*)?=[[:space:]]*b?"[a-zA-Z0-9]{16,}"' + '(const|static)[[:space:]]+(mut[[:space:]]+)?[A-Za-z0-9_]*TOKEN[A-Za-z0-9_]*[[:space:]]*(:[^=]*)?=[[:space:]]*b?"[^"]{8,}"' + '(let|static)[[:space:]]+(mut[[:space:]]+)?[a-z0-9_]*api_key[a-z0-9_]*[[:space:]]*(:[^=]*)?=[[:space:]]*b?"[^"]{8,}"' + 'HMAC.*"[a-fA-F0-9]{32,}"' + '[a-z0-9_]*password[a-z0-9_]*[[:space:]]*(:[^=]*)?=[[:space:]]*b?"[^"]{8,}"' + ) + + # Exemptions match on the VALUE or on an explicit pragma, never on a + # file path: a path-based exemption would blind the job to a genuine + # secret in the same file. + # 1. env-lookup RHS — `env::var("NAME")` IS the practice this job + # exists to enforce, so flagging it fails the correct code. + # 2. URL values — an OAuth endpoint is public, not a credential. + # 3. comment lines — documentation of a pattern is not a secret. + # 4. inline pragma `// scanner-allow: rust-secrets`. + COMMENT='^[^:]*:[0-9][0-9]*:[[:space:]]*(//|/\*|\*)' + ENV_RHS='=[^;]*(env::var|env::var_os|std::env|env!\(|option_env!\(|from_env|dotenv)' + URL_RHS='=[[:space:]]*b?"(https?|wss?|ftp)://' + PRAGMA='scanner-allow:[[:space:]]*rust-secrets' + + blocking=0 + advisory=0 + for pattern in "${PATTERNS[@]}"; do + hits="$(grep -nHE "$pattern" "${FILES[@]}" 2>/dev/null \ + | grep -vE "$COMMENT" \ + | grep -vE "$ENV_RHS" \ + | grep -vE "$URL_RHS" \ + | grep -vE "$PRAGMA" || [ $? -eq 1 ])" + [ -n "$hits" ] || continue + + # ./src hits keep blocking. Hits outside it are newly visible, so + # they warn until the cutoff rather than reddening repositories + # whose wider tree was never actually being scanned. + old_scope="$(printf '%s\n' "$hits" | grep -E '^\./src/' || [ $? -eq 1 ])" + new_scope="$(printf '%s\n' "$hits" | grep -vE '^\./src/' || [ $? -eq 1 ])" + + if [ -n "$old_scope" ]; then + printf '%s\n' "$old_scope" + echo "WARNING: Potential hardcoded secret found matching: $pattern" + blocking=1 + fi + if [ -n "$new_scope" ]; then + printf '%s\n' "$new_scope" + if [[ "$TODAY" < "$ENFORCE_RUST_WIDE_SCAN_FROM" ]]; then + echo "::warning::Potential hardcoded secret outside ./src matching:" \ + "$pattern — becomes BLOCKING on $ENFORCE_RUST_WIDE_SCAN_FROM (today is $TODAY)." + advisory=1 + else + echo "WARNING: Potential hardcoded secret found matching: $pattern" + blocking=1 + fi + fi + done + + if [ "$blocking" -eq 1 ]; then + echo "::error::Potential hardcoded secrets detected. Use environment variables instead." + exit 1 + fi + if [ "$advisory" -eq 1 ]; then + # Never claim a clean pass while findings are outstanding. + echo "NOT YET ENFORCED: findings outside ./src are advisory until $ENFORCE_RUST_WIDE_SCAN_FROM." + exit 0 + fi + echo 'No hardcoded secrets detected in Rust sources.' + + shell-secrets: + name: "scan / shell-secrets" + runs-on: ubuntu-24.04 + timeout-minutes: 10 permissions: contents: read - uses: hyperpolymath/standards/.github/workflows/secret-scanner-reusable.yml@571cc734cd69fb846032ec77a662aa8ee4fc32cd - secrets: inherit + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + with: + persist-credentials: false + + - name: Run repository secret pattern hook + run: | + set -euo pipefail + rc=0 + git grep -IlE '(ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{82}|glpat-[A-Za-z0-9_-]{20,}|AKIA[0-9A-Z]{16}|-----BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY-----)' > "$RUNNER_TEMP/hits.txt" || rc=$? + # git grep exits 1 when nothing matches; above 1 is an error. + if [ "$rc" -gt 1 ]; then + echo "::error::Secret pattern scan failed with exit ${rc}" + exit 2 + fi + # Exclude intentional scanner rules and adversarial negative test fixtures. + filtered="$(grep -vE '^(\.github/hooks/scan-secrets\.sh|\.github/workflows/secret-scanner\.yml|\.machine_readable/testing/adversarial-content\.k9\.ncl|tests/)' "$RUNNER_TEMP/hits.txt" || :)" + if [ -n "$filtered" ]; then + echo "::error::Hardcoded credential material detected:" + printf '%s\n' "$filtered" + exit 1 + fi + echo "No hardcoded shell/source credentials detected." diff --git a/.github/workflows/security-policy.yml b/.github/workflows/security-policy.yml index 0793bec..d8e11ba 100644 --- a/.github/workflows/security-policy.yml +++ b/.github/workflows/security-policy.yml @@ -1,7 +1,7 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. name: Security Policy on: push: diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index a44effb..4d850e4 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -1,12 +1,22 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. # SonarQube Cloud (SonarCloud) static analysis. Analysis scope + exclusions live # in sonar-project.properties. Requires the SONAR_TOKEN repository secret # (Settings -> Secrets and variables -> Actions) and a SonarCloud project: -# https://sonarcloud.io/project/overview?id=hyperpolymath_rsr-template-repo +# https://sonarcloud.io/project/overview?id=hyperpolymath_systemet # Mirrors the boj-server arrangement. +# +# The token guard is the guard of `sonarqube.yml` in the estate template +# (hyperpolymath/rsr-template-repo, added there on 2026-07-28). +# With no SONAR_TOKEN secret, `secrets.SONAR_TOKEN` is the empty string, and the +# scanner fails with "Not authorized or organization not found". That failure +# says nothing about the code: the scan did not run. +# With the guard, the job says that the scan is not configured, and it exits +# with 0. When a SONAR_TOKEN secret exists, the scan runs, with no edit of this +# file. The automatic analysis of SonarCloud (the check "SonarCloud Code +# Analysis") does not use this workflow. name: SonarQube on: push: @@ -25,11 +35,33 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 steps: + - name: Report configuration state + id: cfg + # A job-level `if:` cannot read a secret. This step reads the token + # and gives the result to the steps after it. The token is in the + # environment of this step and of the scan step only. + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + # The script of this step is Bash. `shell: bash` says so in the step: + # a default shell or a runner with a different shell cannot change it. + shell: bash + run: | + if [ -z "${SONAR_TOKEN}" ]; then + echo "configured=false" >> "$GITHUB_OUTPUT" + echo "::notice::SONAR_TOKEN is not configured for this repository - skipping the CI-based scan." + echo "SonarCloud automatic analysis (check: 'SonarCloud Code Analysis') is unaffected." + echo "To enable this scan: Settings > Secrets and variables > Actions > new secret SONAR_TOKEN." + else + echo "configured=true" >> "$GITHUB_OUTPUT" + echo "SONAR_TOKEN present - running the CI-based scan." + fi - name: Checkout + if: steps.cfg.outputs.configured == 'true' uses: actions/checkout@v7.0.1 with: fetch-depth: 0 # full history for accurate new-code detection - name: SonarQube Scan + if: steps.cfg.outputs.configured == 'true' uses: SonarSource/sonarqube-scan-action@v8.4.1 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/static-analysis-gate.yml b/.github/workflows/static-analysis-gate.yml index 16bb1fd..3170817 100644 --- a/.github/workflows/static-analysis-gate.yml +++ b/.github/workflows/static-analysis-gate.yml @@ -1,7 +1,7 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. # Static Analysis Gate — Required by branch protection rules. # Runs panic-attack and hypatia, deposits findings for gitbot-fleet learning. name: Static Analysis Gate diff --git a/.github/workflows/wellknown-enforcement.yml b/.github/workflows/wellknown-enforcement.yml index fd68126..128c448 100644 --- a/.github/workflows/wellknown-enforcement.yml +++ b/.github/workflows/wellknown-enforcement.yml @@ -1,7 +1,7 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. +# This workflow is managed by gh actions-lock. name: Well-Known Standards (RFC 9116 + RSR) on: push: diff --git a/.github/workflows/workflow-linter.yml b/.github/workflows/workflow-linter.yml index fb8bb24..7106aa6 100644 --- a/.github/workflows/workflow-linter.yml +++ b/.github/workflows/workflow-linter.yml @@ -1,7 +1,7 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. +# 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: Workflow Security Linter diff --git a/.machine_readable/0.1-AI-MANIFEST.a2ml b/.machine_readable/0.1-AI-MANIFEST.a2ml index 7d8ce9e..a28ee37 100644 --- a/.machine_readable/0.1-AI-MANIFEST.a2ml +++ b/.machine_readable/0.1-AI-MANIFEST.a2ml @@ -12,14 +12,14 @@ description: | automation scripts. canonical_locations: - state: "6a2/STATE.a2ml" - meta: "6a2/META.a2ml" - ecosystem: "6a2/ECOSYSTEM.a2ml" - agentic: "6a2/AGENTIC.a2ml" - neurosym: "6a2/NEUROSYM.a2ml" - playbook: "6a2/PLAYBOOK.a2ml" - clade: "6a2/CLADE.a2ml" - anchors: "6a2/anchors/" + state: "descriptiles/STATE.a2ml" + meta: "descriptiles/META.a2ml" + ecosystem: "descriptiles/ECOSYSTEM.a2ml" + agentic: "descriptiles/AGENTIC.a2ml" + neurosym: "descriptiles/NEUROSYM.a2ml" + playbook: "descriptiles/PLAYBOOK.a2ml" + clade: "descriptiles/CLADE.a2ml" + anchors: "descriptiles/anchors/" policies: "policies/" ai_configs: "ai/" compliance: "compliance/" diff --git a/.machine_readable/ai/.clinerules b/.machine_readable/ai/.clinerules index 4818351..32565bc 100644 --- a/.machine_readable/ai/.clinerules +++ b/.machine_readable/ai/.clinerules @@ -2,7 +2,7 @@ # Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) # Authoritative source: docs/AI-CONVENTIONS.md -# STARTUP: Read 0-AI-MANIFEST.a2ml first, then .machine_readable/6a2/STATE.a2ml. +# STARTUP: Read 0-AI-MANIFEST.a2ml first, then .machine_readable/descriptiles/STATE.a2ml. # LICENSE # All original code: MPL-2.0. diff --git a/.machine_readable/ai/.windsurfrules b/.machine_readable/ai/.windsurfrules index 4818351..32565bc 100644 --- a/.machine_readable/ai/.windsurfrules +++ b/.machine_readable/ai/.windsurfrules @@ -2,7 +2,7 @@ # Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) # Authoritative source: docs/AI-CONVENTIONS.md -# STARTUP: Read 0-AI-MANIFEST.a2ml first, then .machine_readable/6a2/STATE.a2ml. +# STARTUP: Read 0-AI-MANIFEST.a2ml first, then .machine_readable/descriptiles/STATE.a2ml. # LICENSE # All original code: MPL-2.0. diff --git a/.machine_readable/ai/AI.a2ml b/.machine_readable/ai/AI.a2ml index 3754aca..ed3cd3c 100644 --- a/.machine_readable/ai/AI.a2ml +++ b/.machine_readable/ai/AI.a2ml @@ -7,8 +7,8 @@ - Prefer to keep generated files out of source control, and regenerate them with the documented commands before committing. ## Workflow -1. Inspect `.machine_readable/6a2/STATE.a2ml` for blockers and next actions. -2. Respect any constraints listed inside `.machine_readable/6a2/AGENTIC.a2ml` when tooling changes are requested. +1. Inspect `.machine_readable/descriptiles/STATE.a2ml` for blockers and next actions. +2. Respect any constraints listed inside `.machine_readable/descriptiles/AGENTIC.a2ml` when tooling changes are requested. 3. After finishing edits, update STATE with your outcomes and commit with a concise, imperative message. ## Delivery Promises diff --git a/.machine_readable/ai/README.adoc b/.machine_readable/ai/README.adoc index 29d3c06..a5dec59 100644 --- a/.machine_readable/ai/README.adoc +++ b/.machine_readable/ai/README.adoc @@ -16,9 +16,9 @@ Avoid scattering agent instruction files around the repo root. Recommended machine read order: -* `.machine_readable/6a2/anchors/ANCHOR.a2ml` +* `.machine_readable/descriptiles/anchors/ANCHOR.a2ml` * `.machine_readable/policies/MAINTENANCE-AXES.a2ml` * `.machine_readable/policies/MAINTENANCE-CHECKLIST.a2ml` * `.machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml` -* `.machine_readable/6a2/STATE.a2ml` -* `.machine_readable/6a2/META.a2ml` +* `.machine_readable/descriptiles/STATE.a2ml` +* `.machine_readable/descriptiles/META.a2ml` diff --git a/.machine_readable/arrival-pack/claude-md.k9.ncl b/.machine_readable/arrival-pack/claude-md.k9.ncl index 969e8bf..d3fbb1c 100644 --- a/.machine_readable/arrival-pack/claude-md.k9.ncl +++ b/.machine_readable/arrival-pack/claude-md.k9.ncl @@ -34,11 +34,11 @@ K9! # Inputs the projection reads (single source of truth). inputs = { descriptiles = [ - ".machine_readable/6a2/CLADE.a2ml", - ".machine_readable/6a2/ECOSYSTEM.a2ml", - ".machine_readable/6a2/AGENTIC.a2ml", - ".machine_readable/6a2/STATE.a2ml", - ".machine_readable/6a2/anchors/ANCHOR.a2ml", + ".machine_readable/descriptiles/CLADE.a2ml", + ".machine_readable/descriptiles/ECOSYSTEM.a2ml", + ".machine_readable/descriptiles/AGENTIC.a2ml", + ".machine_readable/descriptiles/STATE.a2ml", + ".machine_readable/descriptiles/anchors/ANCHOR.a2ml", ], estate_common = "hyperpolymath/manifesto@", # DRAFT-unratified today target = "CLAUDE.md", diff --git a/.machine_readable/arrival-pack/extract.sh b/.machine_readable/arrival-pack/extract.sh index b77a8d5..c0adf70 100755 --- a/.machine_readable/arrival-pack/extract.sh +++ b/.machine_readable/arrival-pack/extract.sh @@ -13,7 +13,7 @@ # Determinism (no timestamps) is required so the k9 drift check can byte-compare. set -euo pipefail -DIR6A2="${1:-.machine_readable/6a2}" +DIR6A2="${1:-.machine_readable/descriptiles}" CLADE="$DIR6A2/CLADE.a2ml" ECO="$DIR6A2/ECOSYSTEM.a2ml" AGENTIC="$DIR6A2/AGENTIC.a2ml" diff --git a/.machine_readable/arrival-pack/generate.sh b/.machine_readable/arrival-pack/generate.sh index 3caafa9..ac2a859 100755 --- a/.machine_readable/arrival-pack/generate.sh +++ b/.machine_readable/arrival-pack/generate.sh @@ -13,7 +13,7 @@ ROOT="$(git rev-parse --show-toplevel)" AP="$ROOT/.machine_readable/arrival-pack" TARGET="$ROOT/CLAUDE.md" -bash "$AP/extract.sh" "$ROOT/.machine_readable/6a2" > "$AP/claude-md-data.json" +bash "$AP/extract.sh" "$ROOT/.machine_readable/descriptiles" > "$AP/claude-md-data.json" nickel export --format raw "$AP/arrival-pack.ncl" > "$AP/.region.tmp" if [ -f "$TARGET" ] && grep -q 'ARRIVAL-PACK:BEGIN' "$TARGET"; then diff --git a/.machine_readable/arrival-pack/verify.sh b/.machine_readable/arrival-pack/verify.sh index 0763f00..1d90acf 100755 --- a/.machine_readable/arrival-pack/verify.sh +++ b/.machine_readable/arrival-pack/verify.sh @@ -13,7 +13,7 @@ TARGET="$ROOT/CLAUDE.md" [ -f "$TARGET" ] || { echo "DRIFT: CLAUDE.md missing — run \`just claude-md\`"; exit 1; } -bash "$AP/extract.sh" "$ROOT/.machine_readable/6a2" > "$AP/claude-md-data.json" +bash "$AP/extract.sh" "$ROOT/.machine_readable/descriptiles" > "$AP/claude-md-data.json" fresh="$(nickel export --format raw "$AP/arrival-pack.ncl")" committed="$(awk '/