Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion .github/workflows/governance-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ jobs:
path: .standards-checkout
sparse-checkout: |
scripts
tools/policy/check-language-policy.sh
sparse-checkout-cone-mode: false

- name: Run Hypatia scan (Baseline validation)
Expand Down Expand Up @@ -364,9 +365,10 @@ jobs:
repository: hyperpolymath/standards
ref: main
path: .standards-checkout
# Sparse-checkout only the scripts dir to keep this fast.
# Include the shared policy gate as well as the scripts it complements.
sparse-checkout: |
scripts
tools/policy/check-language-policy.sh
sparse-checkout-cone-mode: false

- name: Check for TypeScript
Expand All @@ -384,6 +386,19 @@ jobs:
# the .ts is a separate follow-up after the dual-target window.
run: deno run --allow-read --no-lock .standards-checkout/scripts/check-ts-allowlist.deno.js

- name: Check language-policy invariants
run: |
SCRIPT=".standards-checkout/tools/policy/check-language-policy.sh"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if [ ! -f "$SCRIPT" ] && [ -f tools/policy/check-language-policy.sh ]; then
SCRIPT="tools/policy/check-language-policy.sh"
echo "Using this repository's own copy (standards self-check)."
fi
if [ ! -f "$SCRIPT" ]; then
echo "::error::language-policy gate not found in standards@main or locally"
exit 1
fi
bash "$SCRIPT"

- name: check-ts-allowlist source/compile drift (informational)
# Non-blocking — informational until the AffineScript compiler
# output is hash-pinned per compiler version. The compiler header
Expand Down Expand Up @@ -1109,6 +1124,7 @@ jobs:
sparse-checkout: |
scripts/check-workflow-duplicate-keys.sh
scripts/update-actions-lock.sh
tools/policy/check-workflows-parse.sh
sparse-checkout-cone-mode: false
# ⚠ Not fatal if the file is absent. This checkout is pinned to
# standards@main, so during a rename of the script the fetch finds
Expand All @@ -1118,6 +1134,19 @@ jobs:
# fallback in the next step.
continue-on-error: true

- name: Parse every tracked workflow
run: |
SCRIPT=".standards-dupkey/tools/policy/check-workflows-parse.sh"
if [ ! -f "$SCRIPT" ] && [ -f tools/policy/check-workflows-parse.sh ]; then
SCRIPT="tools/policy/check-workflows-parse.sh"
echo "Using this repository's own copy (standards self-lint)."
fi
if [ ! -f "$SCRIPT" ]; then
echo "::error::workflow parser gate not found in standards@main or locally"
exit 1
fi
bash "$SCRIPT"

- name: Duplicate YAML keys in workflows
run: |
# GitHub Actions REJECTS a workflow with duplicate keys: the run is
Expand Down
109 changes: 109 additions & 0 deletions scripts/tests/policy-gates-test.sh
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'
79 changes: 79 additions & 0 deletions tools/policy/check-language-policy.sh
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

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaBNAltt5JHUX0F3hA7h&open=AaBNAltt5JHUX0F3hA7h&pullRequest=690
echo "no CLAUDE.md tracked - nothing to check"
exit 0
fi

fail() {

Check warning on line 14 in tools/policy/check-language-policy.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit return statement at the end of the function.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaBNAltt5JHUX0F3hA7i&open=AaBNAltt5JHUX0F3hA7i&pullRequest=690
printf ' FAIL %s\n %s\n' "$1" "$2"

Check warning on line 15 in tools/policy/check-language-policy.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Assign this positional parameter to a local variable.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaBNAltt5JHUX0F3hA7k&open=AaBNAltt5JHUX0F3hA7k&pullRequest=690

Check warning on line 15 in tools/policy/check-language-policy.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Assign this positional parameter to a local variable.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaBNAltt5JHUX0F3hA7j&open=AaBNAltt5JHUX0F3hA7j&pullRequest=690
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

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit return statement at the end of the function.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaBNAltt5JHUX0F3hA7l&open=AaBNAltt5JHUX0F3hA7l&pullRequest=690
sed -E '/^[[:space:]]*>/d; s/"[^"]*"//g; s/“[^”]*”//g' "$1"

Check warning on line 23 in tools/policy/check-language-policy.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Assign this positional parameter to a local variable.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaBNAltt5JHUX0F3hA7m&open=AaBNAltt5JHUX0F3hA7m&pullRequest=690
}

for file in "${files[@]}"; do
case "$file" in

Check failure on line 27 in tools/policy/check-language-policy.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add a default case (*) to handle unexpected values.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaBNAltt5JHUX0F3hA7n&open=AaBNAltt5JHUX0F3hA7n&pullRequest=690
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?'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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 \b or escaping the dot is safer to avoid matching unintended extensions like .tsx.

Suggested change
typescript_runtime='Executes .\.ts. directly|JS/TS runtime|[Ss]upports? TypeScript|[Rr]uns? [^[:alnum:][:space:]]*\.ts[^[:alnum:][:space:]]* files?'
typescript_runtime='Executes .\.ts\\b directly|JS/TS runtime|[Ss]upports? TypeScript|[Rr]uns? [^[:alnum:][:space:]]*\.ts[^[:alnum:][:space:]]* files?'

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

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaBNAltt5JHUX0F3hA7o&open=AaBNAltt5JHUX0F3hA7o&pullRequest=690
echo "language policy OK"
else
echo "Language-policy drift detected. Fix the local copy; do not weaken this gate."
fi
exit "$status"
65 changes: 65 additions & 0 deletions tools/policy/check-workflows-parse.sh
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

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Redirect this error message to stderr (>&2).

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaBNAlnO5JHUX0F3hA7V&open=AaBNAlnO5JHUX0F3hA7V&pullRequest=690
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ LOW RISK

Nitpick: Simplify the git ls-files patterns to avoid duplicate results. The ** glob already covers the root level.

)

if [ "${#workflows[@]}" -eq 0 ]; then

Check failure on line 16 in tools/policy/check-workflows-parse.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaBNAlnO5JHUX0F3hA7W&open=AaBNAlnO5JHUX0F3hA7W&pullRequest=690
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

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Redirect this error message to stderr (>&2).

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaBNAlnO5JHUX0F3hA7X&open=AaBNAlnO5JHUX0F3hA7X&pullRequest=690
exit 1
fi

parse_ok() {

Check warning on line 33 in tools/policy/check-workflows-parse.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit return statement at the end of the function.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaBNAlnO5JHUX0F3hA7Y&open=AaBNAlnO5JHUX0F3hA7Y&pullRequest=690
case "$parser" in

Check failure on line 34 in tools/policy/check-workflows-parse.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add a default case (*) to handle unexpected values.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaBNAlnO5JHUX0F3hA7Z&open=AaBNAlnO5JHUX0F3hA7Z&pullRequest=690
yq) yq '.' "$1" >/dev/null 2>&1 ;;

Check warning on line 35 in tools/policy/check-workflows-parse.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Assign this positional parameter to a local variable.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaBNAlnO5JHUX0F3hA7a&open=AaBNAlnO5JHUX0F3hA7a&pullRequest=690
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

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Assign this positional parameter to a local variable.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaBNAlnO5JHUX0F3hA7b&open=AaBNAlnO5JHUX0F3hA7b&pullRequest=690
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

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Assign this positional parameter to a local variable.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaBNAlnO5JHUX0F3hA7c&open=AaBNAlnO5JHUX0F3hA7c&pullRequest=690
esac
}

has_forbidden_control() {

Check warning on line 41 in tools/policy/check-workflows-parse.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit return statement at the end of the function.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaBNAlnO5JHUX0F3hA7d&open=AaBNAlnO5JHUX0F3hA7d&pullRequest=690
od -An -v -tu1 "$1" | awk '

Check warning on line 42 in tools/policy/check-workflows-parse.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Assign this positional parameter to a local variable.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaBNAlnO5JHUX0F3hA7e&open=AaBNAlnO5JHUX0F3hA7e&pullRequest=690
{ 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

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaBNAlnO5JHUX0F3hA7f&open=AaBNAlnO5JHUX0F3hA7f&pullRequest=690
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

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaBNAlnO5JHUX0F3hA7g&open=AaBNAlnO5JHUX0F3hA7g&pullRequest=690
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"
Loading