diff --git a/.githooks/validate-spdx-workflows.sh b/.githooks/validate-spdx-workflows.sh index 76f8f784f..1d3e814c2 100755 --- a/.githooks/validate-spdx-workflows.sh +++ b/.githooks/validate-spdx-workflows.sh @@ -12,9 +12,14 @@ validate_file() { # Check for SPDX header in first non-comment line HAS_SPDX=false - while IFS= read -r line; do + while IFS= read -r line || [ $? -eq 0 ]; do [[ "$line" =~ ^[[:space:]]*$ ]] && continue - [[ "$line" =~ ^[[:space:]]*# ]] && { echo "$line" | grep -qE 'SPDX-License-Identifier' && HAS_SPDX=true; continue; } + [[ "$line" =~ ^[[:space:]]*# ]] && { + if echo "$line" | grep -qE 'SPDX-License-Identifier'; then + HAS_SPDX=true + continue + fi + } break done < "$file" diff --git a/.github/workflows/hypatia-scan-reusable.yml b/.github/workflows/hypatia-scan-reusable.yml index 3a70113d6..0f5d3ead3 100644 --- a/.github/workflows/hypatia-scan-reusable.yml +++ b/.github/workflows/hypatia-scan-reusable.yml @@ -113,10 +113,10 @@ jobs: id: scan run: | set -euo pipefail - # Exactly one JSON array of findings, each with a recognised severity. - # Missing/truncated output is a scanner error, never an empty clean scan. - if [ ! -s hypatia-findings.json ] || ! jq -e ' - type == "array" and length > 0 and all(.[]; + # Exactly one JSON document that is a flat array of findings, each with a recognised severity. + # An empty array [] means no findings; multiple documents or missing/truncated output is a scanner error. + if [ ! -s hypatia-findings.json ] || ! jq -e -s ' + length == 1 and (.[0] | type == "array" and all(.[]; type == "object" and (.severity as $s | ["critical", "high", "medium", "low", "info", "informational"] | index($s) != null)) ' hypatia-findings.json >/dev/null; then diff --git a/0-ai-gatekeeper-protocol/.github/dependabot.yml b/0-ai-gatekeeper-protocol/.github/dependabot.yml index b4d5ec67a..5dd9725e5 100644 --- a/0-ai-gatekeeper-protocol/.github/dependabot.yml +++ b/0-ai-gatekeeper-protocol/.github/dependabot.yml @@ -14,40 +14,18 @@ updates: patterns: - "*" open-pull-requests-limit: 2 - # Rust/Cargo - package-ecosystem: "cargo" - directory: "/" - schedule: - interval: "weekly" ignore: - dependency-name: "*" update-types: ["version-update:semver-patch"] open-pull-requests-limit: 0 - # Elixir/Mix - package-ecosystem: "mix" - directory: "/" - schedule: - interval: "weekly" open-pull-requests-limit: 3 - # Node.js/npm - package-ecosystem: "npm" - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 3 - # Python/pip - package-ecosystem: "pip" - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 3 - # Nix flakes - package-ecosystem: "nix" - directory: "/" - schedule: - interval: "weekly" diff --git a/rhodium-standard-repositories/.github/dependabot.yml b/rhodium-standard-repositories/.github/dependabot.yml index b41b7b872..e01680b0b 100644 --- a/rhodium-standard-repositories/.github/dependabot.yml +++ b/rhodium-standard-repositories/.github/dependabot.yml @@ -11,6 +11,3 @@ updates: - "*" open-pull-requests-limit: 2 - package-ecosystem: "nix" - directory: "/" - schedule: - interval: "weekly" diff --git a/rhodium-standard-repositories/satellites/cccp/.github/dependabot.yml b/rhodium-standard-repositories/satellites/cccp/.github/dependabot.yml index b4d5ec67a..5dd9725e5 100644 --- a/rhodium-standard-repositories/satellites/cccp/.github/dependabot.yml +++ b/rhodium-standard-repositories/satellites/cccp/.github/dependabot.yml @@ -14,40 +14,18 @@ updates: patterns: - "*" open-pull-requests-limit: 2 - # Rust/Cargo - package-ecosystem: "cargo" - directory: "/" - schedule: - interval: "weekly" ignore: - dependency-name: "*" update-types: ["version-update:semver-patch"] open-pull-requests-limit: 0 - # Elixir/Mix - package-ecosystem: "mix" - directory: "/" - schedule: - interval: "weekly" open-pull-requests-limit: 3 - # Node.js/npm - package-ecosystem: "npm" - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 3 - # Python/pip - package-ecosystem: "pip" - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 3 - # Nix flakes - package-ecosystem: "nix" - directory: "/" - schedule: - interval: "weekly" diff --git a/rhodium-standard-repositories/satellites/cccp/satellites/nextgen-languages/7-tentacles/.github/dependabot.yml b/rhodium-standard-repositories/satellites/cccp/satellites/nextgen-languages/7-tentacles/.github/dependabot.yml index b41b7b872..e01680b0b 100644 --- a/rhodium-standard-repositories/satellites/cccp/satellites/nextgen-languages/7-tentacles/.github/dependabot.yml +++ b/rhodium-standard-repositories/satellites/cccp/satellites/nextgen-languages/7-tentacles/.github/dependabot.yml @@ -11,6 +11,3 @@ updates: - "*" open-pull-requests-limit: 2 - package-ecosystem: "nix" - directory: "/" - schedule: - interval: "weekly" diff --git a/rhodium-standard-repositories/satellites/cccp/satellites/php-aegis/.github/dependabot.yml b/rhodium-standard-repositories/satellites/cccp/satellites/php-aegis/.github/dependabot.yml index 89c0c4755..a88329af3 100644 --- a/rhodium-standard-repositories/satellites/cccp/satellites/php-aegis/.github/dependabot.yml +++ b/rhodium-standard-repositories/satellites/cccp/satellites/php-aegis/.github/dependabot.yml @@ -1,32 +1,2 @@ +# SPDX-License-Identifier: MPL-2.0 version: 2 -updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" - groups: - actions: - patterns: - - "*" - open-pull-requests-limit: 2 - - - package-ecosystem: "cargo" - directory: "/" - schedule: - interval: "weekly" - ignore: - - dependency-name: "*" - update-types: ["version-update:semver-patch"] - open-pull-requests-limit: 0 - - - package-ecosystem: "npm" - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 3 - - - package-ecosystem: "pip" - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 3 diff --git a/rhodium-standard-repositories/satellites/cccp/satellites/sanctify-php/.github/dependabot.yml b/rhodium-standard-repositories/satellites/cccp/satellites/sanctify-php/.github/dependabot.yml index 89c0c4755..a88329af3 100644 --- a/rhodium-standard-repositories/satellites/cccp/satellites/sanctify-php/.github/dependabot.yml +++ b/rhodium-standard-repositories/satellites/cccp/satellites/sanctify-php/.github/dependabot.yml @@ -1,32 +1,2 @@ +# SPDX-License-Identifier: MPL-2.0 version: 2 -updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" - groups: - actions: - patterns: - - "*" - open-pull-requests-limit: 2 - - - package-ecosystem: "cargo" - directory: "/" - schedule: - interval: "weekly" - ignore: - - dependency-name: "*" - update-types: ["version-update:semver-patch"] - open-pull-requests-limit: 0 - - - package-ecosystem: "npm" - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 3 - - - package-ecosystem: "pip" - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 3 diff --git a/rhodium-standard-repositories/satellites/mustfile/.github/dependabot.yml b/rhodium-standard-repositories/satellites/mustfile/.github/dependabot.yml index b4d5ec67a..5dd9725e5 100644 --- a/rhodium-standard-repositories/satellites/mustfile/.github/dependabot.yml +++ b/rhodium-standard-repositories/satellites/mustfile/.github/dependabot.yml @@ -14,40 +14,18 @@ updates: patterns: - "*" open-pull-requests-limit: 2 - # Rust/Cargo - package-ecosystem: "cargo" - directory: "/" - schedule: - interval: "weekly" ignore: - dependency-name: "*" update-types: ["version-update:semver-patch"] open-pull-requests-limit: 0 - # Elixir/Mix - package-ecosystem: "mix" - directory: "/" - schedule: - interval: "weekly" open-pull-requests-limit: 3 - # Node.js/npm - package-ecosystem: "npm" - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 3 - # Python/pip - package-ecosystem: "pip" - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 3 - # Nix flakes - package-ecosystem: "nix" - directory: "/" - schedule: - interval: "weekly" diff --git a/rhodium-standard-repositories/satellites/palimpsest-license/.github/dependabot.yml b/rhodium-standard-repositories/satellites/palimpsest-license/.github/dependabot.yml index f9f73bdbd..a88329af3 100644 --- a/rhodium-standard-repositories/satellites/palimpsest-license/.github/dependabot.yml +++ b/rhodium-standard-repositories/satellites/palimpsest-license/.github/dependabot.yml @@ -1,34 +1,2 @@ +# SPDX-License-Identifier: MPL-2.0 version: 2 -updates: - # 1. Maintain GitHub Actions (workflows) - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "daily" - # THE MAGIC PART: Groups updates into one PR - groups: - dependencies: - patterns: - - "*" - open-pull-requests-limit: 2 - - # 2. Maintain Rust (cargo) - - package-ecosystem: "cargo" - directory: "/" - schedule: - interval: "daily" - groups: - dependencies: - patterns: - - "*" - open-pull-requests-limit: 0 - - # 3. Maintain NPM (if you have package.json) - - package-ecosystem: "deno" - directory: "/" - schedule: - interval: "daily" - groups: - dependencies: - patterns: - - "*" diff --git a/rhodium-standard-repositories/satellites/robot-repo-automaton/.github/dependabot.yml b/rhodium-standard-repositories/satellites/robot-repo-automaton/.github/dependabot.yml index 89c0c4755..a88329af3 100644 --- a/rhodium-standard-repositories/satellites/robot-repo-automaton/.github/dependabot.yml +++ b/rhodium-standard-repositories/satellites/robot-repo-automaton/.github/dependabot.yml @@ -1,32 +1,2 @@ +# SPDX-License-Identifier: MPL-2.0 version: 2 -updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" - groups: - actions: - patterns: - - "*" - open-pull-requests-limit: 2 - - - package-ecosystem: "cargo" - directory: "/" - schedule: - interval: "weekly" - ignore: - - dependency-name: "*" - update-types: ["version-update:semver-patch"] - open-pull-requests-limit: 0 - - - package-ecosystem: "npm" - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 3 - - - package-ecosystem: "pip" - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 3 diff --git a/rhodium-standard-repositories/satellites/rsr-certifier/.github/dependabot.yml b/rhodium-standard-repositories/satellites/rsr-certifier/.github/dependabot.yml index 4593e59b1..a88329af3 100644 --- a/rhodium-standard-repositories/satellites/rsr-certifier/.github/dependabot.yml +++ b/rhodium-standard-repositories/satellites/rsr-certifier/.github/dependabot.yml @@ -1,36 +1,2 @@ +# SPDX-License-Identifier: MPL-2.0 version: 2 -updates: - # Rust dependencies - - package-ecosystem: "cargo" - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 5 - labels: - - "dependencies" - - "rust" - commit-message: - prefix: "chore(deps)" - - # GitHub Actions - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" - labels: - - "dependencies" - - "ci" - commit-message: - prefix: "chore(ci)" - open-pull-requests-limit: 2 - - # Docker - - package-ecosystem: "docker" - directory: "/container" - schedule: - interval: "daily" - labels: - - "dependencies" - - "container" - commit-message: - prefix: "chore(container)" diff --git a/rhodium-standard-repositories/satellites/rsr-deployer/.github/dependabot.yml b/rhodium-standard-repositories/satellites/rsr-deployer/.github/dependabot.yml index b4d5ec67a..5dd9725e5 100644 --- a/rhodium-standard-repositories/satellites/rsr-deployer/.github/dependabot.yml +++ b/rhodium-standard-repositories/satellites/rsr-deployer/.github/dependabot.yml @@ -14,40 +14,18 @@ updates: patterns: - "*" open-pull-requests-limit: 2 - # Rust/Cargo - package-ecosystem: "cargo" - directory: "/" - schedule: - interval: "weekly" ignore: - dependency-name: "*" update-types: ["version-update:semver-patch"] open-pull-requests-limit: 0 - # Elixir/Mix - package-ecosystem: "mix" - directory: "/" - schedule: - interval: "weekly" open-pull-requests-limit: 3 - # Node.js/npm - package-ecosystem: "npm" - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 3 - # Python/pip - package-ecosystem: "pip" - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 3 - # Nix flakes - package-ecosystem: "nix" - directory: "/" - schedule: - interval: "weekly" diff --git a/rhodium-standard-repositories/satellites/state.scm/.github/dependabot.yml b/rhodium-standard-repositories/satellites/state.scm/.github/dependabot.yml index b41b7b872..e01680b0b 100644 --- a/rhodium-standard-repositories/satellites/state.scm/.github/dependabot.yml +++ b/rhodium-standard-repositories/satellites/state.scm/.github/dependabot.yml @@ -11,6 +11,3 @@ updates: - "*" open-pull-requests-limit: 2 - package-ecosystem: "nix" - directory: "/" - schedule: - interval: "weekly" diff --git a/scripts/check-descriptile-policy.sh b/scripts/check-descriptile-policy.sh new file mode 100755 index 000000000..d055bf098 --- /dev/null +++ b/scripts/check-descriptile-policy.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# A CI policy must not require files that the structural-drift gate forbids. +set -euo pipefail +status=0 +while IFS= read -r -d '' file; do + [[ -f "$file" ]] || continue + # Restrict this check to executable file-existence tests. Historical prose + # and commented examples are not policy enforcement. + if awk ' + /^[[:space:]]*#/ { next } + { + source=$0 + # Mask quoted prose while retaining literal path arguments. Keep command + # substitutions visible: an echo can still execute a file test in $(). + code=""; quote=""; quoted="" + for (i=1; i<=length(source); i++) { + ch=substr(source,i,1) + if (quote != "") { + if (ch == quote) { + if (quoted ~ /^\.machine_readable\/(6a2\/)?(STATE|META|ECOSYSTEM|AGENTIC|NEUROSYM|PLAYBOOK|ANCHOR)\.a2ml$/ || + (quote == "\"" && quoted ~ /\$\(|`/)) code=code quoted + else code=code " " + quote=""; quoted="" + } else if (ch == "\\" && quote == "\"") { + quoted=quoted ch substr(source,++i,1) + } else quoted=quoted ch + } else if (ch == "\"" || ch == sprintf("%c",39)) quote=ch + else code=code ch + } + # A multiline shell quote cannot be classified from this physical line. + if (quote != "") code=code quoted + if (code ~ /(-f[[:space:]]|-e[[:space:]]|check_file[[:space:]])/ && + code ~ /\.machine_readable\/(6a2\/)?(STATE|META|ECOSYSTEM|AGENTIC|NEUROSYM|PLAYBOOK|ANCHOR)\.a2ml/) { + found=1; print FNR ":" source + } + } + END { exit !found } + ' "$file"; then + printf '::error file=%s::Policy requires a retired descriptile path; use .machine_readable/descriptiles/ and reconcile existing files\n' "$file" + status=1 + fi +done < <(git ls-files -z -- '.github/workflows/*.yml' '.github/workflows/*.yaml' 'scripts/*.sh' '.githooks/*.sh' Justfile justfile) +exit "$status" diff --git a/scripts/tests/science-ci-security-test.rb b/scripts/tests/science-ci-security-test.rb index 37ec2aa3f..eab03a9fe 100755 --- a/scripts/tests/science-ci-security-test.rb +++ b/scripts/tests/science-ci-security-test.rb @@ -107,7 +107,7 @@ def workflow(name) assert(File.read(output).include?('critical=1'), 'critical finding was lost') assert(File.read(output).include?('high=1'), 'high finding was lost') # Test invalid inputs - flat array format - ['', '[', '[]', '[{}]', '[{"severity":"unknown"}]'].each do |invalid| + ['', '[', '[] []', '[{}]', '[{"severity":"unknown"}]'].each do |invalid| FileUtils.rm_f(output) File.write(findings, invalid) _out, _err, status = Open3.capture3(env, 'bash', '-c', step.fetch('run'), chdir: tmp) diff --git a/tools/policy/check-workflows-parse.sh b/tools/policy/check-workflows-parse.sh index 5d99eae50..86a05933c 100755 --- a/tools/policy/check-workflows-parse.sh +++ b/tools/policy/check-workflows-parse.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 # Fail if any tracked GitHub Actions workflow does not parse as YAML. set -uo pipefail @@ -62,6 +63,20 @@ has_forbidden_control() { ' } +has_no_jobs() { + case "$parser" in + yq) + yq -e '.jobs == null or (.jobs | type) != "!!map"' "$1" >/dev/null 2>&1 + ;; + python) + python3 -c 'import sys,yaml; d=yaml.safe_load(open(sys.argv[1], encoding="utf-8")) or {}; jobs=d.get("jobs"); sys.exit(0 if jobs is not None and isinstance(jobs, dict) else 1)' "$1" + ;; + ruby) + ruby -ryaml -e 'd=YAML.safe_load(File.read(ARGV[0]), aliases: true) || {}; jobs=d["jobs"]; exit(jobs.nil? || !jobs.is_a?(Hash) ? 0 : 1)' "$1" + ;; + esac +} + status=0 for file in "${workflows[@]}"; do [ -f "$file" ] || continue @@ -71,6 +86,9 @@ for file in "${workflows[@]}"; do if has_forbidden_control "$file"; then echo ' contains a YAML-forbidden control character' fi + elif has_no_jobs "$file"; then + status=1 + printf '::error file=%s::workflow has no jobs; a workflow without jobs produces no check run\n' "$file" elif has_reusable_timeout "$file"; then status=1 printf '%s\n' "::error file=$file::a reusable-workflow call job cannot declare timeout-minutes; GitHub rejects it before creating any jobs"