-
-
Notifications
You must be signed in to change notification settings - Fork 0
fix(governance): make policy validation fail closed #690
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
|
|
||
| repo_root=$(git rev-parse --show-toplevel) | ||
| language_gate="$repo_root/tools/policy/check-language-policy.sh" | ||
| workflow_gate="$repo_root/tools/policy/check-workflows-parse.sh" | ||
| fixture=$(mktemp -d) | ||
| trap 'rm -rf "$fixture"' EXIT | ||
|
|
||
| init_fixture() { | ||
| local target=$1 | ||
| mkdir -p "$target" | ||
| git -C "$target" init -q | ||
| git -C "$target" config user.email tests@example.invalid | ||
| git -C "$target" config user.name 'Policy gate tests' | ||
| } | ||
|
|
||
| write_policy() { | ||
| local target=$1 extra=${2-} | ||
| mkdir -p "$target/.claude" | ||
| { | ||
| printf '%s\n' '### ALLOWED' '| **Bun** | JS runtime |' | ||
| printf '%s\n' '### BANNED' '| **Deno** | **Bun** |' | ||
| printf '%s\n' "$extra" | ||
| } > "$target/.claude/CLAUDE.md" | ||
| git -C "$target" add .claude/CLAUDE.md | ||
| } | ||
|
|
||
| expect_pass() { "$@" >/dev/null; } | ||
| expect_fail() { if "$@" >/dev/null 2>&1; then echo "expected failure: $*" >&2; exit 1; fi; } | ||
|
|
||
| quoted="$fixture/quoted" | ||
| init_fixture "$quoted" | ||
| write_policy "$quoted" 'History: "Supports TypeScript" and “JS/TS runtime”.' | ||
| (cd "$quoted" && expect_pass "$language_gate") | ||
|
|
||
| mixed="$fixture/mixed" | ||
| init_fixture "$mixed" | ||
| write_policy "$mixed" 'History: "Supports TypeScript"; live policy Supports TypeScript.' | ||
| (cd "$mixed" && expect_fail "$language_gate") | ||
|
|
||
| blockquote="$fixture/blockquote" | ||
| init_fixture "$blockquote" | ||
| write_policy "$blockquote" '> Historical policy Supports TypeScript.' | ||
| (cd "$blockquote" && expect_pass "$language_gate") | ||
|
|
||
| quoted_invariants="$fixture/quoted-invariants" | ||
| init_fixture "$quoted_invariants" | ||
| write_policy "$quoted_invariants" '> | | historical blank |' | ||
| printf '%s\n' '> **No new files**' >> "$quoted_invariants/.claude/CLAUDE.md" | ||
| git -C "$quoted_invariants" add .claude/CLAUDE.md | ||
| (cd "$quoted_invariants" && expect_pass "$language_gate") | ||
|
|
||
| blank="$fixture/blank" | ||
| init_fixture "$blank" | ||
| write_policy "$blank" '| | replacement |' | ||
| (cd "$blank" && expect_fail "$language_gate") | ||
|
|
||
| missing_bun="$fixture/missing-bun" | ||
| init_fixture "$missing_bun" | ||
| mkdir -p "$missing_bun/.claude" | ||
| printf '%s\n' '### ALLOWED' '### BANNED' '| **Deno** | **Bun** |' > "$missing_bun/.claude/CLAUDE.md" | ||
| git -C "$missing_bun" add .claude/CLAUDE.md | ||
| (cd "$missing_bun" && expect_fail "$language_gate") | ||
|
|
||
| missing_deno="$fixture/missing-deno" | ||
| init_fixture "$missing_deno" | ||
| mkdir -p "$missing_deno/.claude" | ||
| printf '%s\n' '### ALLOWED' '| **Bun** | JS runtime |' '### BANNED' > "$missing_deno/.claude/CLAUDE.md" | ||
| git -C "$missing_deno" add .claude/CLAUDE.md | ||
| (cd "$missing_deno" && expect_fail "$language_gate") | ||
|
|
||
| no_workflows="$fixture/no-workflows" | ||
| init_fixture "$no_workflows" | ||
| mkdir -p "$no_workflows/bin" | ||
| ln -s "$(command -v git)" "$no_workflows/bin/git" | ||
| (cd "$no_workflows" && PATH="$no_workflows/bin" expect_pass /bin/bash "$workflow_gate") | ||
|
|
||
| without_parser="$fixture/without-parser" | ||
| init_fixture "$without_parser" | ||
| mkdir -p "$without_parser/.github/workflows" "$without_parser/bin" | ||
| printf '%s\n' 'name: test' 'on: push' 'jobs: {}' > "$without_parser/.github/workflows/test.yml" | ||
| git -C "$without_parser" add .github/workflows/test.yml | ||
| ln -s "$(command -v git)" "$without_parser/bin/git" | ||
| (cd "$without_parser" && PATH="$without_parser/bin" expect_fail /bin/bash "$workflow_gate") | ||
|
|
||
| valid="$fixture/valid" | ||
| init_fixture "$valid" | ||
| mkdir -p "$valid/.github/workflows" | ||
| printf '%s\n' 'name: test' 'on: push' 'jobs: {}' > "$valid/.github/workflows/test.yml" | ||
| git -C "$valid" add .github/workflows/test.yml | ||
| (cd "$valid" && expect_pass "$workflow_gate") | ||
|
|
||
| invalid="$fixture/invalid" | ||
| init_fixture "$invalid" | ||
| mkdir -p "$invalid/.github/workflows" | ||
| printf '%s\n' 'name: test' 'jobs: [' > "$invalid/.github/workflows/test.yml" | ||
| git -C "$invalid" add .github/workflows/test.yml | ||
| (cd "$invalid" && expect_fail "$workflow_gate") | ||
|
|
||
| control="$fixture/control" | ||
| init_fixture "$control" | ||
| mkdir -p "$control/.github/workflows" | ||
| printf 'name: test\001\non: push\njobs: {}\n' > "$control/.github/workflows/test.yml" | ||
| git -C "$control" add .github/workflows/test.yml | ||
| control_output=$(cd "$control" && "$workflow_gate" 2>&1 || true) | ||
| grep -q 'contains a YAML-forbidden control character' <<<"$control_output" | ||
|
|
||
| echo 'policy gate controls passed' |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,79 @@ | ||||||
| #!/usr/bin/env bash | ||||||
| # Assert the invariant parts of the estate language policy in tracked CLAUDE.md files. | ||||||
| set -uo pipefail | ||||||
|
|
||||||
| status=0 | ||||||
| declare -a files=() | ||||||
| mapfile -d '' -t files < <(git ls-files -z -- '*CLAUDE.md') | ||||||
|
|
||||||
| if [ "${#files[@]}" -eq 0 ]; then | ||||||
|
Check failure on line 9 in tools/policy/check-language-policy.sh
|
||||||
| echo "no CLAUDE.md tracked - nothing to check" | ||||||
| exit 0 | ||||||
| fi | ||||||
|
|
||||||
| fail() { | ||||||
|
Check warning on line 14 in tools/policy/check-language-policy.sh
|
||||||
| printf ' FAIL %s\n %s\n' "$1" "$2" | ||||||
|
Check warning on line 15 in tools/policy/check-language-policy.sh
|
||||||
| status=1 | ||||||
| } | ||||||
|
|
||||||
| # Historical policy text is often retained in Markdown quotes. Remove blockquotes and | ||||||
| # quoted substrings while preserving the rest of each line, so a live violation after a | ||||||
| # historical quotation is still visible. | ||||||
| live_lines() { | ||||||
|
Check warning on line 22 in tools/policy/check-language-policy.sh
|
||||||
| sed -E '/^[[:space:]]*>/d; s/"[^"]*"//g; s/“[^”]*”//g' "$1" | ||||||
|
Check warning on line 23 in tools/policy/check-language-policy.sh
|
||||||
| } | ||||||
|
|
||||||
| for file in "${files[@]}"; do | ||||||
| case "$file" in | ||||||
|
Check failure on line 27 in tools/policy/check-language-policy.sh
|
||||||
| node_modules/*|*/node_modules/*) continue ;; | ||||||
| esac | ||||||
|
|
||||||
| echo "checking $file" | ||||||
| live=$(live_lines "$file") | ||||||
|
|
||||||
| if grep -nF -- '| Bun | Deno |' <<<"$live" >/dev/null; then | ||||||
| fail "$file" 'Bun is listed as banned with Deno as its replacement.' | ||||||
| fi | ||||||
| if grep -F 'No package.json for runtime deps' <<<"$live" >/dev/null; then | ||||||
| fail "$file" 'Policy forbids the dependency manifest that Bun requires.' | ||||||
| fi | ||||||
| if grep -F 'deno.json imports' <<<"$live" >/dev/null; then | ||||||
| fail "$file" 'Policy directs runtime dependencies into deno.json.' | ||||||
| fi | ||||||
|
|
||||||
| typescript_runtime='Executes .\.ts. directly|JS/TS runtime|[Ss]upports? TypeScript|[Rr]uns? [^[:alnum:][:space:]]*\.ts[^[:alnum:][:space:]]* files?' | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⚪ LOW RISK The trailing dot in the first alternation matches any character. Using a word boundary
Suggested change
|
||||||
| if grep -E "$typescript_runtime" <<<"$live" >/dev/null; then | ||||||
| fail "$file" 'Policy advertises TypeScript execution.' | ||||||
| fi | ||||||
|
|
||||||
| if awk -F'|' 'NF >= 4 && $2 ~ /^[[:space:]]*$/ { found=1 } END { exit !found }' <<<"$live"; then | ||||||
| fail "$file" 'Policy table contains an empty first cell (a blanking scar).' | ||||||
| fi | ||||||
| if grep -F '| **** |' <<<"$live" >/dev/null; then | ||||||
| fail "$file" 'Policy table contains an empty bold cell.' | ||||||
| fi | ||||||
| if grep -E '\*\*No new +files\*\*|Only where +cannot' <<<"$live" >/dev/null; then | ||||||
| fail "$file" 'Enforcement text contains a blanked language name.' | ||||||
| fi | ||||||
| if grep -E '^\|[[:space:]]*AffineScript[[:space:]]*\|[[:space:]]*AffineScript[[:space:]]*\|' <<<"$live" >/dev/null; then | ||||||
| fail "$file" 'The banned table maps AffineScript to itself.' | ||||||
| fi | ||||||
|
|
||||||
| if grep -qE '^### (ALLOWED|BANNED)' <<<"$live"; then | ||||||
| if ! grep -qE '^\|[[:space:]]*\*\*Bun\*\*[[:space:]]*\|' <<<"$live" && | ||||||
| ! grep -qiE '^[-*][[:space:]]+\*{0,2}Bun\*{0,2}([[:space:]]|$)' <<<"$live"; then | ||||||
| fail "$file" 'No Bun entry appears in the allowed policy.' | ||||||
| fi | ||||||
| if ! grep -qE '^\|[[:space:]]*\*{0,2}Deno\*{0,2}[[:space:]]*\|[[:space:]]*\*{0,2}Bun\*{0,2}[[:space:]]*\|' <<<"$live" && | ||||||
| ! grep -qiE '^[-*][[:space:]]+Deno[[:space:]]*\(use Bun\)' <<<"$live"; then | ||||||
| fail "$file" 'Deno is not listed as banned with Bun as its replacement.' | ||||||
| fi | ||||||
| fi | ||||||
| done | ||||||
|
|
||||||
| if [ "$status" -eq 0 ]; then | ||||||
|
Check failure on line 74 in tools/policy/check-language-policy.sh
|
||||||
| echo "language policy OK" | ||||||
| else | ||||||
| echo "Language-policy drift detected. Fix the local copy; do not weaken this gate." | ||||||
| fi | ||||||
| exit "$status" | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| #!/usr/bin/env bash | ||
| # Fail if any tracked GitHub Actions workflow does not parse as YAML. | ||
| set -uo pipefail | ||
|
|
||
| if ! command -v git >/dev/null 2>&1; then | ||
| echo '::error::git is required to enumerate tracked workflows' | ||
|
Check warning on line 6 in tools/policy/check-workflows-parse.sh
|
||
| exit 1 | ||
| fi | ||
|
|
||
| declare -a workflows=() | ||
| mapfile -d '' -t workflows < <( | ||
| git ls-files -z -- '.github/workflows/*.yml' '.github/workflows/*.yaml' \ | ||
| '**/.github/workflows/*.yml' '**/.github/workflows/*.yaml' | ||
|
Comment on lines
+12
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⚪ LOW RISK Nitpick: Simplify the |
||
| ) | ||
|
|
||
| if [ "${#workflows[@]}" -eq 0 ]; then | ||
|
Check failure on line 16 in tools/policy/check-workflows-parse.sh
|
||
| echo "no workflows tracked - nothing to check" | ||
| exit 0 | ||
| fi | ||
|
|
||
| parser='' | ||
| if command -v yq >/dev/null 2>&1; then | ||
| parser=yq | ||
| elif command -v python3 >/dev/null 2>&1 && python3 -c 'import yaml' >/dev/null 2>&1; then | ||
| parser=python | ||
| elif command -v ruby >/dev/null 2>&1; then | ||
| parser=ruby | ||
| else | ||
| echo "::error::no YAML parser available (yq, python3+pyyaml, or ruby)" | ||
|
Check warning on line 29 in tools/policy/check-workflows-parse.sh
|
||
| exit 1 | ||
| fi | ||
|
|
||
| parse_ok() { | ||
|
Check warning on line 33 in tools/policy/check-workflows-parse.sh
|
||
| case "$parser" in | ||
|
Check failure on line 34 in tools/policy/check-workflows-parse.sh
|
||
| yq) yq '.' "$1" >/dev/null 2>&1 ;; | ||
|
Check warning on line 35 in tools/policy/check-workflows-parse.sh
|
||
| python) python3 -c 'import sys,yaml; yaml.safe_load(open(sys.argv[1], encoding="utf-8"))' "$1" >/dev/null 2>&1 ;; | ||
|
Check warning on line 36 in tools/policy/check-workflows-parse.sh
|
||
| ruby) ruby -ryaml -e 'YAML.safe_load(File.read(ARGV[0]), aliases: true)' "$1" >/dev/null 2>&1 ;; | ||
|
Check warning on line 37 in tools/policy/check-workflows-parse.sh
|
||
| esac | ||
| } | ||
|
|
||
| has_forbidden_control() { | ||
|
Check warning on line 41 in tools/policy/check-workflows-parse.sh
|
||
| od -An -v -tu1 "$1" | awk ' | ||
|
Check warning on line 42 in tools/policy/check-workflows-parse.sh
|
||
| { for (i=1; i<=NF; i++) if (($i < 9) || ($i > 10 && $i < 13) || ($i > 13 && $i < 32)) found=1 } | ||
| END { exit !found } | ||
| ' | ||
| } | ||
|
|
||
| status=0 | ||
| for file in "${workflows[@]}"; do | ||
| [ -f "$file" ] || continue | ||
|
Check failure on line 50 in tools/policy/check-workflows-parse.sh
|
||
| if ! parse_ok "$file"; then | ||
| status=1 | ||
| printf '::error file=%s::workflow does not parse; an unloaded workflow produces no check run\n' "$file" | ||
| if has_forbidden_control "$file"; then | ||
| echo ' contains a YAML-forbidden control character' | ||
| fi | ||
| fi | ||
| done | ||
|
|
||
| if [ "$status" -eq 0 ]; then | ||
|
Check failure on line 60 in tools/policy/check-workflows-parse.sh
|
||
| echo "all ${#workflows[@]} workflow(s) parse" | ||
| else | ||
| echo 'At least one workflow cannot load. Fix the YAML; do not delete the check.' | ||
| fi | ||
| exit "$status" | ||
Uh oh!
There was an error while loading. Please reload this page.