|
1 | | -# SPDX-License-Identifier: PMPL-1.0-or-later |
| 1 | +# SPDX-License-Identifier: PMPL-1.0 |
2 | 2 | name: Language Policy Enforcement |
3 | | -on: [push, pull_request] |
| 3 | + |
| 4 | +on: |
| 5 | + push: |
| 6 | + branches: [main, master] |
| 7 | + pull_request: |
| 8 | + branches: [main, master] |
| 9 | + |
| 10 | +# Estate guardrail: cancel superseded runs so re-pushes / rebased PR |
| 11 | +# updates do not pile up queued runs against the shared account-wide |
| 12 | +# Actions concurrency pool. Applied only to read-only check workflows |
| 13 | +# (no publish/mutation), so cancelling a superseded run is always safe. |
| 14 | +concurrency: |
| 15 | + group: ${{ github.workflow }}-${{ github.ref }} |
| 16 | + cancel-in-progress: true |
| 17 | + |
| 18 | +permissions: |
| 19 | + contents: read |
| 20 | + |
4 | 21 | jobs: |
5 | | - check: |
| 22 | + check-banned-languages: |
| 23 | + name: Check for Banned Languages |
6 | 24 | runs-on: ubuntu-latest |
7 | 25 | steps: |
8 | | - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 |
9 | | - - name: Enforce language policies |
10 | | - run: | |
11 | | - # Block new Python files (except SaltStack) |
12 | | - NEW_PY=$(git diff --name-only --diff-filter=A HEAD~1 2>/dev/null | grep -E '\.py$' | grep -v 'salt' || true) |
13 | | - if [ -n "$NEW_PY" ]; then |
14 | | - echo "❌ New Python files detected. Use Rust or AffineScript instead." |
15 | | - echo "$NEW_PY" |
16 | | - exit 1 |
17 | | - fi |
18 | | - |
19 | | - # Block new Ruby files |
20 | | - NEW_RB=$(git diff --name-only --diff-filter=A HEAD~1 2>/dev/null | grep -E '\.rb$' || true) |
21 | | - if [ -n "$NEW_RB" ]; then |
22 | | - echo "❌ New Ruby files detected. Use Rust, Ada/SPARK, or Crystal instead." |
23 | | - echo "$NEW_RB" |
24 | | - exit 1 |
25 | | - fi |
26 | | - |
27 | | - # Block new Perl files |
28 | | - NEW_PL=$(git diff --name-only --diff-filter=A HEAD~1 2>/dev/null | grep -E '\.(pl|pm)$' || true) |
29 | | - if [ -n "$NEW_PL" ]; then |
30 | | - echo "❌ New Perl files detected. Use Rust instead." |
31 | | - echo "$NEW_PL" |
32 | | - exit 1 |
33 | | - fi |
34 | | - |
35 | | - # Block new Java/Kotlin (except in LSP projects) |
36 | | - if [[ ! "$GITHUB_REPOSITORY" =~ "language-server" ]]; then |
37 | | - NEW_JAVA=$(git diff --name-only --diff-filter=A HEAD~1 2>/dev/null | grep -E '\.(java|kt)$' || true) |
38 | | - if [ -n "$NEW_JAVA" ]; then |
39 | | - echo "❌ New Java/Kotlin files detected. Use Rust instead." |
40 | | - echo "$NEW_JAVA" |
| 26 | + - name: Checkout |
| 27 | + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 |
| 28 | + |
| 29 | + # TypeScript check delegated to rsr-antipattern.yml (which honours the |
| 30 | + # universal allowlist and the .claude/CLAUDE.md exemptions table). The |
| 31 | + # blunt `find -name "*.ts"` form previously here false-positived on |
| 32 | + # legitimate bridge files (e.g. tests/*.vitest.config.ts under the |
| 33 | + # *vscode*/tests/ allowlist). |
| 34 | + |
| 35 | + - name: Check for ReScript files |
| 36 | + run: | |
| 37 | + # Estate policy: RS/TS/JS -> AffineScript -> typed-wasm. |
| 38 | + # ReScript (.res) is no longer the TS replacement. |
| 39 | + if find . -name "*.res" | grep -v node_modules | head -1 | grep -q .; then |
| 40 | + echo "::error::ReScript files found. Use AffineScript instead." |
| 41 | + find . -name "*.res" | grep -v node_modules |
| 42 | + exit 1 |
| 43 | + fi |
| 44 | + echo "✓ No ReScript files found" |
| 45 | +
|
| 46 | + - name: Check for Go files |
| 47 | + run: | |
| 48 | + if find . -name "*.go" | head -1 | grep -q .; then |
| 49 | + echo "::error::Go files found. Use Rust instead." |
| 50 | + find . -name "*.go" |
| 51 | + exit 1 |
| 52 | + fi |
| 53 | + echo "✓ No Go files found" |
| 54 | +
|
| 55 | + - name: Check for Python files (except Ansible) |
| 56 | + run: | |
| 57 | + # Allow Python only in ansible/ directories or for Ansible-specific files |
| 58 | + PYTHON_FILES=$(find . -name "*.py" | grep -v __pycache__ | grep -v ".venv" | grep -v "ansible" | grep -v "molecule" || true) |
| 59 | + if [ -n "$PYTHON_FILES" ]; then |
| 60 | + echo "::error::Python files found outside Ansible context. Rewrite in Rust/AffineScript." |
| 61 | + echo "$PYTHON_FILES" |
| 62 | + exit 1 |
| 63 | + fi |
| 64 | + echo "✓ No unauthorized Python files found" |
| 65 | +
|
| 66 | + - name: Check for Makefiles |
| 67 | + run: | |
| 68 | + MAKEFILES=$(find . -name "Makefile" -o -name "Makefile.*" -o -name "*.mk" | grep -v ".github" || true) |
| 69 | + if [ -n "$MAKEFILES" ]; then |
| 70 | + echo "::error::Makefiles found. Use Mustfile/justfile instead." |
| 71 | + echo "$MAKEFILES" |
| 72 | + exit 1 |
| 73 | + fi |
| 74 | + echo "✓ No Makefiles found" |
| 75 | +
|
| 76 | + - name: Check for package.json (npm/node) |
| 77 | + run: | |
| 78 | + if [ -f "package.json" ]; then |
| 79 | + # Allow if it only contains devDependencies for tooling |
| 80 | + if grep -q '"dependencies"' package.json; then |
| 81 | + echo "::error::package.json with runtime dependencies found. Use deno.json instead." |
41 | 82 | exit 1 |
42 | 83 | fi |
43 | 84 | fi |
44 | | - |
45 | | - echo "✅ Language policy check passed" |
| 85 | + echo "✓ No npm runtime dependencies found" |
| 86 | +
|
| 87 | + - name: Check for Java/Kotlin files |
| 88 | + run: | |
| 89 | + if find . -name "*.java" -o -name "*.kt" -o -name "*.kts" | head -1 | grep -q .; then |
| 90 | + echo "::error::Java/Kotlin files found. Use Rust/Tauri/Dioxus instead." |
| 91 | + find . -name "*.java" -o -name "*.kt" -o -name "*.kts" |
| 92 | + exit 1 |
| 93 | + fi |
| 94 | + echo "✓ No Java/Kotlin files found" |
| 95 | +
|
| 96 | + - name: Check for Swift files |
| 97 | + run: | |
| 98 | + if find . -name "*.swift" | head -1 | grep -q .; then |
| 99 | + echo "::error::Swift files found. Use Tauri/Dioxus instead." |
| 100 | + find . -name "*.swift" |
| 101 | + exit 1 |
| 102 | + fi |
| 103 | + echo "✓ No Swift files found" |
| 104 | +
|
| 105 | + - name: Check for V-lang code |
| 106 | + run: | |
| 107 | + # V-lang is banned as of 2026-04-10. Migration target: Zig. |
| 108 | + # We detect by v.mod (V-lang's module manifest) rather than *.v |
| 109 | + # extension because .v collides with Verilog hardware sources. |
| 110 | + V_MOD_FILES=$(find . -name "v.mod" -not -path "*/node_modules/*" -not -path "*/.git/*" || true) |
| 111 | + if [ -n "$V_MOD_FILES" ]; then |
| 112 | + echo "::error::V-lang code found (detected via v.mod). V-lang is banned since 2026-04-10. Migrate to Zig." |
| 113 | + echo "$V_MOD_FILES" |
| 114 | + exit 1 |
| 115 | + fi |
| 116 | + # Also check for vpkg.json (alternative V package manifest) |
| 117 | + VPKG_FILES=$(find . -name "vpkg.json" -not -path "*/node_modules/*" -not -path "*/.git/*" || true) |
| 118 | + if [ -n "$VPKG_FILES" ]; then |
| 119 | + echo "::error::V-lang code found (detected via vpkg.json). V-lang is banned since 2026-04-10. Migrate to Zig." |
| 120 | + echo "$VPKG_FILES" |
| 121 | + exit 1 |
| 122 | + fi |
| 123 | + echo "✓ No V-lang code found" |
| 124 | +
|
| 125 | + - name: Check for Flutter/Dart files |
| 126 | + run: | |
| 127 | + if find . -name "*.dart" -o -name "pubspec.yaml" | head -1 | grep -q .; then |
| 128 | + echo "::error::Flutter/Dart code found. Use Tauri/Dioxus instead (Google lock-in policy)." |
| 129 | + find . -name "*.dart" -o -name "pubspec.yaml" |
| 130 | + exit 1 |
| 131 | + fi |
| 132 | + echo "✓ No Flutter/Dart code found" |
| 133 | +
|
| 134 | + - name: Check for ATS2 files |
| 135 | + run: | |
| 136 | + # ATS2 is rejected in favour of Idris2 (formal verification) and |
| 137 | + # Rust/SPARK (safety-critical operational code). See LANGUAGE-POLICY.adoc §Amendments v1.1.0. |
| 138 | + if find . -name "*.dats" -o -name "*.sats" -o -name "*.hats" | head -1 | grep -q .; then |
| 139 | + echo "::error::ATS2 files found. Use Idris2 (formal verification) or Rust/SPARK (safety-critical code) instead." |
| 140 | + find . -name "*.dats" -o -name "*.sats" -o -name "*.hats" |
| 141 | + exit 1 |
| 142 | + fi |
| 143 | + echo "✓ No ATS2 files found" |
| 144 | +
|
| 145 | + check-required-files: |
| 146 | + name: Check Required Files |
| 147 | + runs-on: ubuntu-latest |
| 148 | + steps: |
| 149 | + - name: Checkout |
| 150 | + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 |
| 151 | + |
| 152 | + - name: Check for .machine_readable directory |
| 153 | + run: | |
| 154 | + if [ ! -d ".machine_readable" ]; then |
| 155 | + echo "::warning::.machine_readable/ directory not found" |
| 156 | + else |
| 157 | + echo "✓ .machine_readable/ directory exists" |
| 158 | + # Per estate policy: .a2ml is canonical; .scm is reserved for Guix. |
| 159 | + for a2ml in STATE META ECOSYSTEM AGENTIC NEUROSYM PLAYBOOK; do |
| 160 | + if [ ! -f ".machine_readable/6a2/${a2ml}.a2ml" ] && [ ! -f ".machine_readable/${a2ml}.a2ml" ]; then |
| 161 | + echo "::warning::Missing .machine_readable/6a2/${a2ml}.a2ml (or top-level fallback)" |
| 162 | + else |
| 163 | + echo "✓ ${a2ml}.a2ml present" |
| 164 | + fi |
| 165 | + done |
| 166 | + fi |
| 167 | +
|
| 168 | + - name: Check for Mustfile/justfile |
| 169 | + run: | |
| 170 | + if [ ! -f "Mustfile" ] && [ ! -f "justfile" ]; then |
| 171 | + echo "::warning::Neither Mustfile nor justfile found" |
| 172 | + else |
| 173 | + echo "✓ Build system files present" |
| 174 | + fi |
| 175 | +
|
| 176 | + - name: Check SPDX headers |
| 177 | + run: | |
| 178 | + MISSING_SPDX=0 |
| 179 | + for ext in rs affine js jsx mjs ts tsx py go java kt swift sh bash; do |
| 180 | + while IFS= read -r file; do |
| 181 | + if [ -n "$file" ] && ! head -5 "$file" | grep -q "SPDX-License-Identifier"; then |
| 182 | + echo "::warning::Missing SPDX header: $file" |
| 183 | + MISSING_SPDX=$((MISSING_SPDX + 1)) |
| 184 | + fi |
| 185 | + done < <(find . -name "*.$ext" -type f 2>/dev/null | grep -v node_modules | grep -v .git | head -50) |
| 186 | + done |
| 187 | + if [ $MISSING_SPDX -gt 0 ]; then |
| 188 | + echo "::warning::$MISSING_SPDX files missing SPDX headers" |
| 189 | + fi |
0 commit comments