diff --git a/.cicd-hygiene-allow b/.cicd-hygiene-allow index 227078f..07f059e 100644 --- a/.cicd-hygiene-allow +++ b/.cicd-hygiene-allow @@ -5,5 +5,7 @@ # required-files-check searches for template placeholders, so both necessarily # contain the strings they hunt. This is the same exemption the estate grants a # secret scanner for containing credential-shaped regexes. -actions/code-hygiene-check/action.yml +actions/code-hygiene-check/check.sh +actions/code-hygiene-check/test.sh actions/required-files-check/action.yml +actions/affirmation-check/check.sh diff --git a/.github/workflows/code-hygiene-self-test.yml b/.github/workflows/code-hygiene-self-test.yml new file mode 100644 index 0000000..907c789 --- /dev/null +++ b/.github/workflows/code-hygiene-self-test.yml @@ -0,0 +1,41 @@ +name: Code Hygiene Self-Test + +on: + push: + branches: [main] + paths: + - 'actions/code-hygiene-check/**' + - 'actions/affirmation-check/**' + - 'actions/referencing-check/**' + - 'actions/secrets-check/**' + - 'actions/boj-cartridge-check/**' + - '.github/workflows/code-hygiene-self-test.yml' + pull_request: + paths: + - 'actions/code-hygiene-check/**' + - 'actions/affirmation-check/**' + - 'actions/referencing-check/**' + - 'actions/secrets-check/**' + - 'actions/boj-cartridge-check/**' + - '.github/workflows/code-hygiene-self-test.yml' + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + name: Gate controls + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - run: bash actions/code-hygiene-check/test.sh + - run: bash actions/affirmation-check/test.sh + - run: bash actions/referencing-check/test.sh + - run: bash actions/secrets-check/test.sh + - run: bash actions/boj-cartridge-check/test.sh diff --git a/.github/workflows/main-estate-audit.yml b/.github/workflows/main-estate-audit.yml index b602e97..b6d474c 100755 --- a/.github/workflows/main-estate-audit.yml +++ b/.github/workflows/main-estate-audit.yml @@ -10,82 +10,86 @@ jobs: estate-audit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + # Test the actions from this revision, not the older implementations on + # main. The pinned checkout is the only remote action this job needs. + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Required Files Gate - uses: hyperpolymath/cicd-suite/actions/required-files-check@main + uses: ./actions/required-files-check - name: Code Hygiene Gate - uses: hyperpolymath/cicd-suite/actions/code-hygiene-check@main + uses: ./actions/code-hygiene-check - name: Manifest Validation Gate - uses: hyperpolymath/cicd-suite/actions/manifest-check@main + uses: ./actions/manifest-check - name: Idris2 ABI Purity Gate - uses: hyperpolymath/cicd-suite/actions/idris2-abi-check@main + uses: ./actions/idris2-abi-check - name: Zig Hexadeca API Gate - uses: hyperpolymath/cicd-suite/actions/zig-hexadeca-check@main + uses: ./actions/zig-hexadeca-check - name: Contractile Validation Gate - uses: hyperpolymath/cicd-suite/actions/contractile-validation-check@main + uses: ./actions/contractile-validation-check - name: Recipes Set Validation Gate - uses: hyperpolymath/cicd-suite/actions/recipes-set-check@main + uses: ./actions/recipes-set-check - name: Affirmation Document Gate - uses: hyperpolymath/cicd-suite/actions/affirmation-check@main + uses: ./actions/affirmation-check - name: Academic Referencing Gate - uses: hyperpolymath/cicd-suite/actions/referencing-check@main + uses: ./actions/referencing-check - name: Semantic Audit Gate - uses: hyperpolymath/cicd-suite/actions/semantic-audit-check@main + uses: ./actions/semantic-audit-check - name: SPDX License Gate - uses: hyperpolymath/cicd-suite/actions/spdx-license-check@main + uses: ./actions/spdx-license-check - name: Proof Runner Gate - uses: hyperpolymath/cicd-suite/actions/proof-runner-check@main + uses: ./actions/proof-runner-check - name: PRAT Testing Gate - uses: hyperpolymath/cicd-suite/actions/prat-check@main + uses: ./actions/prat-check - name: Panic Attack & Pons Gate - uses: hyperpolymath/cicd-suite/actions/custom-tools-check@main + uses: ./actions/custom-tools-check - name: WWW & Well-Known Compliance Gate - uses: hyperpolymath/cicd-suite/actions/www-compliance-check@main + uses: ./actions/www-compliance-check - name: BoJ Cartridge Validation Gate - uses: hyperpolymath/cicd-suite/actions/boj-cartridge-check@main + uses: ./actions/boj-cartridge-check - name: Formatting Validation Gate - uses: hyperpolymath/cicd-suite/actions/formatting-check@main + uses: ./actions/formatting-check - name: Accreditations & Badges Gate - uses: hyperpolymath/cicd-suite/actions/badges-check@main + uses: ./actions/badges-check - name: Metrics Extraction Gate - uses: hyperpolymath/cicd-suite/actions/metrics-check@main + uses: ./actions/metrics-check - name: Linguist & Banned Languages Gate - uses: hyperpolymath/cicd-suite/actions/linguist-check@main + uses: ./actions/linguist-check - name: Test & Benchmarks Dashboard Gate - uses: hyperpolymath/cicd-suite/actions/tests-benches-check@main + uses: ./actions/tests-benches-check - name: Hosting & Site Status Gate - uses: hyperpolymath/cicd-suite/actions/hosting-check@main + uses: ./actions/hosting-check - name: Git-Sea Analytics Gate - uses: hyperpolymath/cicd-suite/actions/gitsea-check@main + uses: ./actions/gitsea-check - name: Trust & Humans Validation Gate - uses: hyperpolymath/cicd-suite/actions/trust-humans-check@main + uses: ./actions/trust-humans-check - name: Are We UnAPI Gate (Secret Scanning) - uses: hyperpolymath/cicd-suite/actions/secrets-check@main + uses: ./actions/secrets-check - name: Reasonably Good Token Validation Gate - uses: hyperpolymath/cicd-suite/actions/vaulted-tokens-check@main + uses: ./actions/vaulted-tokens-check diff --git a/actions/affirmation-check/action.yml b/actions/affirmation-check/action.yml index 8172632..7b9cd07 100755 --- a/actions/affirmation-check/action.yml +++ b/actions/affirmation-check/action.yml @@ -1,43 +1,15 @@ name: 'Affirmation Document Gate' -description: 'Validates that the AFFIRMATION document is signed and updated within the last 28 days.' +description: 'Validates an applicable AFFIRMATION snapshot without treating it as a universal or self-proving document.' +inputs: + required: + description: 'Set true only when the repository declares the governance-tier capability.' + required: false + default: 'false' runs: using: 'composite' steps: - name: Run Affirmation Check shell: bash - run: | - echo "Validating AFFIRMATION Document..." - - # Check for existence (can be .md, .adoc, etc.) - aff_file=$(find . -maxdepth 1 -name "AFFIRMATION*" | head -n 1) - if [ -z "$aff_file" ]; then - echo "::error::AFFIRMATION document not found in the repository root." - exit 1 - fi - - echo "Found AFFIRMATION document: $aff_file" - - # Check signature (heuristic: look for 'Signed:', 'Signature:', or PGP block) - if ! grep -E -i 'signed:|signature:|BEGIN PGP SIGNATURE' "$aff_file" > /dev/null; then - echo "::error::AFFIRMATION document does not appear to be signed." - exit 1 - fi - - # Check date (updated within last 28 days) - # First check git log for the file (if it is tracked in git) - last_update_ts=$(git log -1 --format="%at" -- "$aff_file" 2>/dev/null) - - if [ -z "$last_update_ts" ]; then - # Fallback to filesystem mtime if not in git (e.g. during initial setup) - last_update_ts=$(stat -c %Y "$aff_file") - fi - - current_ts=$(date +%s) - diff_days=$(( (current_ts - last_update_ts) / 86400 )) - - if [ "$diff_days" -gt 28 ]; then - echo "::error::AFFIRMATION document is out of date! Last updated $diff_days days ago. Must be updated within 28 days." - exit 1 - fi - - echo "AFFIRMATION Document validation passed. (Updated $diff_days days ago)." + env: + AFFIRMATION_REQUIRED: ${{ inputs.required }} + run: "${{ github.action_path }}/check.sh" diff --git a/actions/affirmation-check/check.sh b/actions/affirmation-check/check.sh new file mode 100755 index 0000000..68a4325 --- /dev/null +++ b/actions/affirmation-check/check.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 + +set -euo pipefail + +root=${GITHUB_WORKSPACE:-.} +required=${AFFIRMATION_REQUIRED:-false} +aff_file= + +for candidate in AFFIRMATION.adoc AFFIRMATION.md AFFIRMATION; do + if [[ -f "$root/$candidate" ]]; then + aff_file=$candidate + break + fi +done + +if [[ -z "$aff_file" ]]; then + if [[ "$required" == "true" ]]; then + echo "::error::AFFIRMATION.adoc is required by the declared governance-tier capability." + exit 1 + fi + echo "::notice::AFFIRMATION is not applicable: governance-tier was not required." + exit 0 +fi + +path=$root/$aff_file +echo "Found AFFIRMATION document: $aff_file" + +substantive_lines=$(awk '!/^[[:space:]]*(#|\/\/|;|$)/ { count++ } END { print count + 0 }' "$path") +if (( substantive_lines < 5 )); then + echo "::error::$aff_file has only $substantive_lines substantive lines; it is a stub, not an affirmation." + exit 1 +fi + +if grep -qiE '\{\{|TODO: update|/dev/null 2>&1; then + signature=$(git -C "$root" log -1 --format='%G?' -- "$aff_file" 2>/dev/null || true) + last_update_ts=$(git -C "$root" log -1 --format='%at' -- "$aff_file" 2>/dev/null || true) +fi + +case "$signature" in + G) echo "Affirmation commit signature verified with a trusted key." ;; + U) echo "::notice::Affirmation commit has a valid signature from an untrusted or locally unknown key." ;; + B|R|E) echo "::error::Affirmation commit signature is bad, revoked, or failed verification."; exit 1 ;; + *) echo "::notice::Affirmation commit signature could not be verified from the available Git history." ;; +esac + +# A dated affirmation is a frozen receipt, not a claim that remains current +# forever. Report age without invalidating historical evidence or forcing an +# empty monthly rewrite. +if [[ -n "$last_update_ts" ]]; then + current_ts=$(date +%s) + age_days=$(( (current_ts - last_update_ts) / 86400 )) + if (( age_days > 28 )); then + echo "::warning::$aff_file is a $age_days-day-old snapshot; verify its anchor before relying on it as current." + else + echo "$aff_file snapshot age: $age_days days." + fi +fi + +echo "AFFIRMATION document validation passed." diff --git a/actions/affirmation-check/test.sh b/actions/affirmation-check/test.sh new file mode 100755 index 0000000..a8ee863 --- /dev/null +++ b/actions/affirmation-check/test.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 + +set -euo pipefail + +here=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +fixture=$(mktemp -d) +trap 'rm -rf -- "$fixture"' EXIT + +GITHUB_WORKSPACE=$fixture AFFIRMATION_REQUIRED=false "$here/check.sh" + +if GITHUB_WORKSPACE=$fixture AFFIRMATION_REQUIRED=true "$here/check.sh"; then + echo "required-but-absent affirmation unexpectedly passed" >&2 + exit 1 +fi + +printf '= AFFIRMATION\n' > "$fixture/AFFIRMATION.adoc" +if GITHUB_WORKSPACE=$fixture AFFIRMATION_REQUIRED=true "$here/check.sh"; then + echo "stub affirmation unexpectedly passed" >&2 + exit 1 +fi + +printf '%s\n' \ + '= AFFIRMATION — controlled fixture' \ + 'This snapshot makes a falsifiable claim.' \ + 'The claim is anchored to a named revision.' \ + 'Tests were run and their scope is stated.' \ + 'Unproved properties are not called proved.' \ + 'Later revisions must be assessed separately.' \ + > "$fixture/AFFIRMATION.adoc" +GITHUB_WORKSPACE=$fixture AFFIRMATION_REQUIRED=true "$here/check.sh" + +echo 'affirmation-check controls passed' diff --git a/actions/boj-cartridge-check/action.yml b/actions/boj-cartridge-check/action.yml index 67e665b..d5d5643 100755 --- a/actions/boj-cartridge-check/action.yml +++ b/actions/boj-cartridge-check/action.yml @@ -1,19 +1,15 @@ name: 'BoJ Cartridge Gate' -description: 'Validates that a cartridge is present for use with the BoJ server.' +description: 'Checks for a BoJ cartridge when BoJ integration is applicable.' +inputs: + required: + description: 'Set true when this repository declares BoJ integration.' + required: false + default: 'false' runs: using: 'composite' steps: - name: Run BoJ Cartridge Check shell: bash - run: | - echo "Validating BoJ Cartridge presence..." - - # Check for typical cartridge files - if [ -f "cartridge.json" ] || [ -f "cartridge.yml" ] || [ -f "cartridge.yaml" ] || [ -d ".cartridges" ] || find . -maxdepth 2 -name "*cartridge*" | grep -q .; then - echo "BoJ cartridge detected." - else - echo "::error::No BoJ cartridge found. A cartridge should be present for use of the tool in full with the BoJ server." - exit 1 - fi - - echo "BoJ Cartridge validation passed." + env: + BOJ_CARTRIDGE_REQUIRED: ${{ inputs.required }} + run: "${{ github.action_path }}/check.sh" diff --git a/actions/boj-cartridge-check/check.sh b/actions/boj-cartridge-check/check.sh new file mode 100755 index 0000000..fb3f230 --- /dev/null +++ b/actions/boj-cartridge-check/check.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 + +set -euo pipefail + +root=${GITHUB_WORKSPACE:-.} +required=${BOJ_CARTRIDGE_REQUIRED:-false} +found= + +for candidate in cartridge.json cartridge.yml cartridge.yaml; do + if [[ -f "$root/$candidate" ]]; then + found=$candidate + break + fi +done +if [[ -z "$found" && -d "$root/.cartridges" ]]; then + found=.cartridges/ +fi + +if [[ -z "$found" ]]; then + if [[ "$required" == "true" ]]; then + echo '::error::A BoJ cartridge is required but no canonical cartridge path exists.' + exit 1 + fi + echo '::notice::BoJ cartridge is not applicable to this repository.' + exit 0 +fi + +echo "BoJ cartridge present at $found." diff --git a/actions/boj-cartridge-check/test.sh b/actions/boj-cartridge-check/test.sh new file mode 100755 index 0000000..c49b78f --- /dev/null +++ b/actions/boj-cartridge-check/test.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 + +set -euo pipefail + +here=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +fixture=$(mktemp -d) +trap 'rm -rf -- "$fixture"' EXIT + +GITHUB_WORKSPACE=$fixture BOJ_CARTRIDGE_REQUIRED=false "$here/check.sh" + +if GITHUB_WORKSPACE=$fixture BOJ_CARTRIDGE_REQUIRED=true "$here/check.sh"; then + echo 'required-but-absent BoJ cartridge unexpectedly passed' >&2 + exit 1 +fi + +printf '%s\n' '{"name":"controlled-fixture"}' > "$fixture/cartridge.json" +GITHUB_WORKSPACE=$fixture BOJ_CARTRIDGE_REQUIRED=true "$here/check.sh" + +echo 'boj-cartridge-check controls passed' diff --git a/actions/code-hygiene-check/action.yml b/actions/code-hygiene-check/action.yml index 64c603b..3617fd9 100755 --- a/actions/code-hygiene-check/action.yml +++ b/actions/code-hygiene-check/action.yml @@ -1,84 +1,8 @@ name: 'Code Hygiene Check' -description: 'Fails on debt markers in source, and on undeclared proof circumventions in proof code.' +description: 'Fails on untracked debt markers in source, and on undeclared proof circumventions in proof code.' runs: using: 'composite' steps: - name: Run Hygiene Check shell: bash - run: | - # Two distinct classes, deliberately scanned differently. - # - # debt markers TODO / FIXME / XXX / HACK / STUB - # circumventions sorry / believe_me / admit / postulate / assert_total - # - # The previous revision ran one case-insensitive, unanchored grep for - # both over the WHOLE tree. That made it unusable: "admit" matched - # "admitted", "sorry" matched ordinary English, any document that - # DISCUSSED a marker failed (including a debt register whose job is to - # track them), and `believe_me` failed repositories whose sanctioned, - # separately-counted axioms are their declared trusted base rather than - # debt. It matched 112 files in one estate repo and 313 in another. - # - # Rules now: - # - case-sensitive: TODO is a marker, "todo" in prose is not - # - whole-word (-w): "admit" no longer matches "admitted" - # - source only: documentation and this repo's own allowlist are out of - # scope, because naming a marker is not committing one - # - circumventions are only meaningful in proof languages - # - a repo may allowlist proof modules that hold sanctioned axioms, on - # the understanding that a dedicated gate counts them (see below) - set -uo pipefail - fail=0 - - ALLOW_FILE=".cicd-hygiene-allow" - allow_args=() - if [ -f "$ALLOW_FILE" ]; then - echo "Using allowlist $ALLOW_FILE:" - while IFS= read -r pat; do - case "$pat" in ''|\#*) continue ;; esac - echo " exclude: $pat" - allow_args+=(":(exclude)$pat") - done < "$ALLOW_FILE" - fi - - # --- Debt markers, source files only ------------------------------ - echo "Scanning source for debt markers (TODO, FIXME, XXX, HACK, STUB)..." - debt=$(git grep -n -E -w 'TODO|FIXME|XXX|HACK|STUB' -- \ - ':(exclude)*.md' ':(exclude)*.adoc' ':(exclude)*.txt' \ - ':(exclude)docs/**' ':(exclude)*.a2ml' \ - ':(exclude)DEBT.md' ':(exclude)'"$ALLOW_FILE" \ - "${allow_args[@]+"${allow_args[@]}"}" 2>/dev/null || true) - if [ -n "$debt" ]; then - echo "::error::Debt markers found in source:" - printf '%s\n' "$debt" - fail=1 - else - echo " none." - fi - - # --- Proof circumventions, proof languages only ------------------- - echo "Scanning proof code for undeclared circumventions..." - circ=$(git grep -n -E -w 'sorry|believe_me|admit|postulate|assert_total' -- \ - '*.idr' '*.lean' '*.v' '*.agda' '*.thy' \ - "${allow_args[@]+"${allow_args[@]}"}" 2>/dev/null || true) - # Idris doc comments (|||) and line comments (--) describe axioms as - # often as they declare them; only real code counts. - circ=$(printf '%s\n' "$circ" | grep -vE ':[0-9]+:[[:space:]]*(\|\|\||--|//|\(\*)' || true) - if [ -n "$circ" ]; then - echo "::error::Undeclared proof circumventions found:" - printf '%s\n' "$circ" - echo "::error::If these are sanctioned axioms, isolate them in one module," - echo "::error::list that module in $ALLOW_FILE, and gate the count with a" - echo "::error::trusted-base check (e.g. scripts/check-trusted-base.sh with" - echo "::error::an EXPECTED_AXIOMS constant). An allowlist without a counter" - echo "::error::is not a sanction, it is a silence." - fail=1 - else - echo " none." - fi - - if [ "$fail" -eq 1 ]; then - echo "::error::Code hygiene gate failed." - exit 1 - fi - echo "Hygiene check passed." + run: bash "$GITHUB_ACTION_PATH/check.sh" diff --git a/actions/code-hygiene-check/check.sh b/actions/code-hygiene-check/check.sh new file mode 100755 index 0000000..118ee6a --- /dev/null +++ b/actions/code-hygiene-check/check.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Distinguish untracked implementation debt from prose, templates, generated +# metadata, and issue-linked work. Proof circumventions remain a separate, +# stricter check over proof languages only. +set -uo pipefail +fail=0 + +readonly allow_file='.cicd-hygiene-allow' +allow_args=() +if [[ -f "$allow_file" ]]; then + echo "Using allowlist $allow_file:" + while IFS= read -r pat || [[ -n "$pat" ]]; do + case "$pat" in + ''|'#'*) continue ;; + *) + echo " exclude: $pat" + allow_args+=(":(exclude)$pat") + ;; + esac + done < "$allow_file" +fi + +# The horizon is deliberately explicit. These are implementation-language +# files, wherever they occur in the tree. Documentation, workflows, +# machine-readable templates, packaging recipes, and container templates are +# not silently relabelled as application source. +exclude_paths=( + ':(exclude).github/**' ':(exclude).githooks/**' + ':(exclude).machine_readable/**' ':(exclude)docs/**' + ':(exclude)packaging/**' ':(exclude)container/**' + ":(exclude)$allow_file" +) + +source_paths=( + '*.adb' '*.ads' '*.affine' '*.agda' '*.bash' '*.c' '*.cc' '*.clj' '*.cljs' + '*.cjs' '*.cpp' '*.cr' '*.cts' '*.cxx' '*.eph' '*.erl' '*.ex' '*.exs' + '*.fish' '*.fs' + '*.fsx' '*.gleam' '*.go' '*.h' '*.hh' '*.hpp' '*.hs' '*.idr' '*.java' + '*.js' '*.jsx' '*.kt' '*.kts' '*.lean' '*.lhs' '*.lua' '*.m' '*.mjs' + '*.ml' '*.mli' '*.mm' '*.mts' '*.nu' + '*.php' '*.pl' '*.pm' '*.py' '*.r' '*.rb' '*.res' '*.resi' '*.roc' '*.rs' + '*.scala' '*.sh' '*.sol' '*.sql' '*.swift' '*.thy' '*.ts' '*.tsx' '*.v' + '*.vala' '*.vapi' '*.zig' + "${exclude_paths[@]}" +) + +echo 'Scanning implementation source for untracked debt markers...' +debt="$({ git grep --untracked -n -E -w 'TODO|FIXME|XXX|HACK|STUB' -- \ + "${source_paths[@]}" "${allow_args[@]+"${allow_args[@]}"}" || true; } \ + | perl -ne 'print if /:[0-9]+:.*\b(?:TODO|FIXME|XXX|HACK|STUB)\b(?!\(#[0-9]+\))/')" +if [[ -n "$debt" ]]; then + echo '::error::Untracked debt markers found in implementation source:' >&2 + printf '%s\n' "$debt" + echo '::error::Resolve the debt or link it as MARKER(#issue).' >&2 + fail=1 +else + echo ' none.' +fi + +echo 'Scanning proof code for undeclared circumventions...' +circ="$({ git grep --untracked -n -E -w 'sorry|believe_me|admit|postulate|assert_total' -- \ + '*.idr' '*.lean' '*.v' '*.agda' '*.thy' \ + "${exclude_paths[@]}" \ + "${allow_args[@]+"${allow_args[@]}"}" || true; } \ + | grep -vE ':[0-9]+:[[:space:]]*(\|\|\||--|//|\(\*)' || true)" +if [[ -n "$circ" ]]; then + echo '::error::Undeclared proof circumventions found:' >&2 + printf '%s\n' "$circ" + echo "::error::If sanctioned, isolate the axioms, list the module in $allow_file," >&2 + echo '::error::and enforce the exact count with a trusted-base check.' >&2 + fail=1 +else + echo ' none.' +fi + +if [[ "$fail" -eq 1 ]]; then + echo '::error::Code hygiene gate failed.' >&2 + exit 1 +fi +echo 'Hygiene check passed.' diff --git a/actions/code-hygiene-check/test.sh b/actions/code-hygiene-check/test.sh new file mode 100755 index 0000000..09addf7 --- /dev/null +++ b/actions/code-hygiene-check/test.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +set -euo pipefail + +checker="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/check.sh" +readonly checker +fixture="$(mktemp -d)" +trap 'rm -rf "$fixture"' EXIT +git -C "$fixture" init -q +git -C "$fixture" config user.email test@example.invalid +git -C "$fixture" config user.name 'Hygiene test' +mkdir -p "$fixture/src" "$fixture/docs" "$fixture/.github/workflows" \ + "$fixture/.machine_readable/templates" + +printf '%s\n' '// clean source' > "$fixture/src/main.rs" +printf '%s\n' 'TODO in documentation is explanatory.' > "$fixture/docs/design.md" +printf '%s\n' 'example = believe_me value' > "$fixture/docs/example.idr" +printf '%s\n' '// marker in the filename is not debt' > "$fixture/src/TODO.rs" +printf '%s\n' '# TODO: workflow follow-up' > "$fixture/.github/workflows/ci.yml" +printf '%s\n' 'TODO: fill this template' > "$fixture/.machine_readable/templates/example.ncl" +git -C "$fixture" add . +git -C "$fixture" commit -qm fixture + +(cd "$fixture" && bash "$checker") + +printf '%s\n' '// TODO: TypeScript implementation debt' > "$fixture/src/app.ts" +if (cd "$fixture" && bash "$checker"); then + echo 'FAIL: untracked TypeScript debt was accepted' >&2 + exit 1 +fi +printf '%s\n' '// clean TypeScript source' > "$fixture/src/app.ts" + +printf '%s\n' '// TODO: untracked implementation debt' > "$fixture/src/main.rs" +if (cd "$fixture" && bash "$checker"); then + echo 'FAIL: untracked source debt was accepted' >&2 + exit 1 +fi + +printf '%s\n' '// TODO(#123): tracked implementation debt' > "$fixture/src/main.rs" +(cd "$fixture" && bash "$checker") + +printf '%s\n' 'proof = believe_me value' > "$fixture/src/Safety.idr" +if (cd "$fixture" && bash "$checker"); then + echo 'FAIL: proof circumvention was accepted' >&2 + exit 1 +fi + +printf '%s' 'src/Safety.idr' > "$fixture/.cicd-hygiene-allow" +(cd "$fixture" && bash "$checker") +echo 'All code-hygiene positive and negative controls passed.' diff --git a/actions/referencing-check/action.yml b/actions/referencing-check/action.yml index 3037476..663d062 100755 --- a/actions/referencing-check/action.yml +++ b/actions/referencing-check/action.yml @@ -1,25 +1,15 @@ name: 'Academic Referencing Gate' -description: 'Validates presence of CITATION.cff, Zotero compatibility, and CiteThemRight standard.' +description: 'Validates the core Citation File Format fields when CITATION.cff is applicable.' +inputs: + required: + description: 'Set true when the repository is required to publish citation metadata.' + required: false + default: 'false' runs: using: 'composite' steps: - name: Run Referencing Check shell: bash - run: | - echo "Validating Academic Referencing Files..." - - cff_file=$(find . -maxdepth 2 -name "CITATION.cff" | head -n 1) - if [ -z "$cff_file" ]; then - echo "::error::CITATION.cff not found. A dedicated referencing file must be present." - exit 1 - fi - - echo "Found referencing file: $cff_file" - - # Check for Zotero compatibility / CiteThemRight mention - # CFF is natively Zotero compatible. We check for a mention of the standard if applicable. - if ! grep -i -q "CiteThemRight" "$cff_file" && ! grep -i -q "CiteThemRight" "README.md" 2>/dev/null; then - echo "::warning::CiteThemRight latest edition should be provided as standard for academic references." - fi - - echo "Academic Referencing Validation Passed." + env: + CITATION_REQUIRED: ${{ inputs.required }} + run: "${{ github.action_path }}/check.sh" diff --git a/actions/referencing-check/check.sh b/actions/referencing-check/check.sh new file mode 100755 index 0000000..3322bcc --- /dev/null +++ b/actions/referencing-check/check.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 + +set -euo pipefail + +root=${GITHUB_WORKSPACE:-.} +required=${CITATION_REQUIRED:-false} +cff=$root/CITATION.cff + +if [[ ! -f "$cff" ]]; then + if [[ "$required" == "true" ]]; then + echo "::error::CITATION.cff is required for this repository." + exit 1 + fi + echo "::notice::CITATION.cff is not applicable to this repository." + exit 0 +fi + +fail=0 +for field in cff-version message title; do + if ! grep -qE "^${field}:[[:space:]]*[^[:space:]]" "$cff"; then + echo "::error::CITATION.cff is missing the required top-level '$field' field." + fail=1 + fi +done + +if ! grep -qE '^authors:[[:space:]]*$' "$cff"; then + echo "::error::CITATION.cff is missing the required top-level 'authors' sequence." + fail=1 +fi + +if ! grep -qE '^[[:space:]]+-[[:space:]]+(family-names|name):[[:space:]]*[^[:space:]]' "$cff"; then + echo "::error::CITATION.cff has no named author entry." + fail=1 +fi + +if (( fail )); then + exit 1 +fi + +echo 'CITATION.cff core-field validation passed.' diff --git a/actions/referencing-check/test.sh b/actions/referencing-check/test.sh new file mode 100755 index 0000000..2e860ee --- /dev/null +++ b/actions/referencing-check/test.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 + +set -euo pipefail + +here=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +fixture=$(mktemp -d) +trap 'rm -rf -- "$fixture"' EXIT + +GITHUB_WORKSPACE=$fixture CITATION_REQUIRED=false "$here/check.sh" + +if GITHUB_WORKSPACE=$fixture CITATION_REQUIRED=true "$here/check.sh"; then + echo 'required-but-absent citation unexpectedly passed' >&2 + exit 1 +fi + +printf '%s\n' 'cff-version: 1.2.0' > "$fixture/CITATION.cff" +if GITHUB_WORKSPACE=$fixture CITATION_REQUIRED=true "$here/check.sh"; then + echo 'malformed citation unexpectedly passed' >&2 + exit 1 +fi + +printf '%s\n' \ + 'cff-version: 1.2.0' \ + 'message: "Please cite this software."' \ + 'title: "Controlled fixture"' \ + 'authors:' \ + ' - family-names: "Example"' \ + ' given-names: "Ada"' \ + > "$fixture/CITATION.cff" +GITHUB_WORKSPACE=$fixture CITATION_REQUIRED=true "$here/check.sh" + +echo 'referencing-check controls passed' diff --git a/actions/required-files-check/action.yml b/actions/required-files-check/action.yml index 1d6a202..421a347 100755 --- a/actions/required-files-check/action.yml +++ b/actions/required-files-check/action.yml @@ -37,7 +37,7 @@ runs: "GOVERNANCE:GOVERNANCE.adoc,GOVERNANCE.md" "ARCHITECTURE:ARCHITECTURE.adoc,ARCHITECTURE.md,docs/architecture/README.adoc,TOPOLOGY.adoc,TOPOLOGY.md" "MAINTAINERS:MAINTAINERS,MAINTAINERS.adoc,MAINTAINERS.md" - "toolchain:.tool-versions,mise.toml" + "toolchain:.tool-versions,.mise.toml,mise.toml" ) declare -A found=() diff --git a/actions/secrets-check/action.yml b/actions/secrets-check/action.yml index 772e6b3..231c8d0 100755 --- a/actions/secrets-check/action.yml +++ b/actions/secrets-check/action.yml @@ -1,24 +1,8 @@ -name: 'Discreet Secret Scanning Gate' -description: 'Silently validates the repository for leaked API keys, tokens, and SSH/PGP private keys.' +name: 'Private Key Material Gate' +description: 'Detects committed or untracked PEM/OpenSSH private-key material in the working tree.' runs: using: 'composite' steps: - name: Run Secret Scanning shell: bash - run: | - echo "Validating repository for exposed secrets..." - - # In a real environment, we would run gitleaks or trufflehog here. - # Check for typical private key extensions/patterns heuristically - if find . -name "*.pem" -o -name "*.key" -o -name "id_rsa" -not -path "*/\.*" | grep -q .; then - echo "::error::Potential private key files detected in repository." - exit 1 - fi - - # Check for inline BEGIN RSA PRIVATE KEY blocks - if git grep -q "BEGIN.*PRIVATE KEY" -- ':!*.md' ':!*.adoc'; then - echo "::error::Inline private key block detected in source code." - exit 1 - fi - - echo "Secret scanning validation passed." + run: "${{ github.action_path }}/check.sh" diff --git a/actions/secrets-check/check.sh b/actions/secrets-check/check.sh new file mode 100755 index 0000000..7e91638 --- /dev/null +++ b/actions/secrets-check/check.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 + +set -euo pipefail + +root=${GITHUB_WORKSPACE:-.} +if ! git -C "$root" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + echo '::error::Private-key scan requires a Git working tree.' + exit 1 +fi + +pattern='-----BEGIN (RSA |DSA |EC |OPENSSH |PGP )?PRIVATE KEY-----' +findings=$(git -C "$root" grep --untracked -I -n -E -e "$pattern" -- . \ + ':(exclude)actions/secrets-check/**' || true) + +if [[ -n "$findings" ]]; then + echo '::error::Private-key material detected in the working tree:' + printf '%s\n' "$findings" + exit 1 +fi + +echo 'No PEM/OpenSSH private-key material detected in tracked or untracked files.' +echo '::notice::This focused gate does not replace GitHub secret scanning or a full history scanner.' diff --git a/actions/secrets-check/test.sh b/actions/secrets-check/test.sh new file mode 100755 index 0000000..69a8c30 --- /dev/null +++ b/actions/secrets-check/test.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 + +set -euo pipefail + +here=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +fixture=$(mktemp -d) +trap 'rm -rf -- "$fixture"' EXIT + +git -C "$fixture" init -q +git -C "$fixture" config user.email test@example.invalid +git -C "$fixture" config user.name 'Secret scan test' +printf '%s\n' 'ordinary configuration' > "$fixture/config.txt" +git -C "$fixture" add config.txt +git -C "$fixture" commit -qm fixture + +GITHUB_WORKSPACE=$fixture "$here/check.sh" + +{ + printf '%s%s\n' '-----BEGIN OPENSSH ' 'PRIVATE KEY-----' + printf '%s\n' \ + 'controlled-positive-marker-not-real-key-material' \ + '-----END OPENSSH PRIVATE KEY-----' +} > "$fixture/leaked.key" +if GITHUB_WORKSPACE=$fixture "$here/check.sh"; then + echo 'planted private-key marker unexpectedly passed' >&2 + exit 1 +fi + +echo 'secrets-check controls passed'