diff --git a/.github/workflows/deno-ci-reusable.yml b/.github/workflows/deno-ci-reusable.yml new file mode 100644 index 00000000..4f7dc363 --- /dev/null +++ b/.github/workflows/deno-ci-reusable.yml @@ -0,0 +1,117 @@ +# SPDX-License-Identifier: PMPL-1.0-or-later +# deno-ci-reusable.yml — Reusable Deno CI bundle (RSR). +# +# Replaces the per-repo `rescript-deno-ci.yml` template that copy-drifted +# across the estate with several systemic bugs: +# +# * `deno test` ran unconditionally and failed `No test modules found` +# on repos with no Deno tests. +# * `deno lint` / `deno fmt --check` ran unconditionally and failed +# `No target files found` on repos with no JS/TS targets. +# * No top-level `permissions:` declaration, tripping the workflow +# security linter in governance-reusable.yml. +# * Still invoked `npx rescript` even though ReScript is banned in new +# code as of 2026-04-30 (see standards/.claude/CLAUDE.md). +# * The vestigial "security" job grep-audited for permission strings +# without doing anything useful with the result. +# +# Each step now early-exits cleanly when the inputs don't exist, so the +# job is a no-op on repos that don't yet ship Deno code but pass cleanly +# when they start to. +# +# Caller example (single wrapper, mirrors governance.yml's pattern): +# +# jobs: +# deno-ci: +# uses: hyperpolymath/standards/.github/workflows/deno-ci-reusable.yml@main + +name: Deno CI (reusable) + +on: + workflow_call: + inputs: + runs-on: + description: Runner label for the deno-ci job + type: string + required: false + default: ubuntu-latest + deno-version: + description: Deno version selector + type: string + required: false + default: v2.x + +permissions: + contents: read + +jobs: + deno-ci: + name: Deno CI + runs-on: ${{ inputs.runs-on }} + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + repository: ${{ github.repository }} + ref: ${{ github.ref }} + + - uses: denoland/setup-deno@e95548e56dfa95d4e1a28d6f422fafe75c4c26fb # v2.0.3 + with: + deno-version: ${{ inputs.deno-version }} + + - name: Detect Deno targets + id: detect + run: | + # `deno.json` (or its legacy `deno.jsonc`) is the canonical + # signal that this repo opts into Deno tooling. We also count + # raw .ts/.js files as a fallback for repos still mid-migration + # to deno.json. Either way, the per-step guards below honour + # any `include` / `exclude` scoping the consumer declared in + # deno.json. + has_config=false + has_targets=false + has_tests=false + if [ -f deno.json ] || [ -f deno.jsonc ]; then has_config=true; fi + if find . -path ./node_modules -prune -o -path ./.git -prune \ + -o \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" -o -name "*.mjs" \) \ + -type f -print 2>/dev/null | head -1 | grep -q .; then + has_targets=true + fi + if find . -path ./node_modules -prune -o -path ./.git -prune \ + -o \( -name "*_test.ts" -o -name "*.test.ts" -o -name "*_test.js" -o -name "*.test.js" \) \ + -type f -print 2>/dev/null | head -1 | grep -q .; then + has_tests=true + fi + echo "has_config=$has_config" >> "$GITHUB_OUTPUT" + echo "has_targets=$has_targets" >> "$GITHUB_OUTPUT" + echo "has_tests=$has_tests" >> "$GITHUB_OUTPUT" + echo "deno-ci: config=$has_config targets=$has_targets tests=$has_tests" + + - name: Deno lint + if: steps.detect.outputs.has_targets == 'true' || steps.detect.outputs.has_config == 'true' + run: deno lint + + - name: Deno fmt check + if: steps.detect.outputs.has_targets == 'true' || steps.detect.outputs.has_config == 'true' + run: deno fmt --check + + - name: Deno test + if: steps.detect.outputs.has_tests == 'true' + run: deno test --allow-all --coverage=coverage + + - name: Deno type check + if: steps.detect.outputs.has_targets == 'true' + # Soft-pass: `deno check` exits non-zero on unresolved imports we + # don't yet require contributors to vendor. We surface output for + # diagnostics but don't fail the gate. + run: deno check . || echo "::warning::deno check reported issues (non-blocking)." + + - name: Summary + run: | + if [ "${{ steps.detect.outputs.has_config }}" = "false" ] \ + && [ "${{ steps.detect.outputs.has_targets }}" = "false" ]; then + echo "deno-ci: no Deno targets detected — skipped all checks." + else + echo "deno-ci: passed (lint / fmt / type / test where applicable)." + fi diff --git a/.github/workflows/governance-reusable.yml b/.github/workflows/governance-reusable.yml index d0d99866..880db774 100644 --- a/.github/workflows/governance-reusable.yml +++ b/.github/workflows/governance-reusable.yml @@ -236,7 +236,7 @@ jobs: # 3. Inline `# hypatia:ignore ...` pragma in the file's first # 8 lines — the same escape the Hypatia scanner itself # honours. - - name: Check for ReScript / Go / Python (banned language files) + - name: Check banned-language files (ReScript / Go / Python / Java / Kotlin / Swift / Dart / V-lang / ATS2 / Makefile) run: | rule_module="cicd_rules" rule_type="banned_language_file" @@ -300,15 +300,36 @@ jobs: echo "✅ No non-exempt ${label}" } - RES_FILES=$(find . -name "*.res" | grep -v node_modules || true) - GO_FILES=$(find . -name "*.go" || true) - PY_FILES=$(find . -name "*.py" | grep -v salt | grep -v _states \ + RES_FILES=$(find . -name "*.res" -not -path "./node_modules/*" -not -path "./.git/*" || true) + GO_FILES=$(find . -name "*.go" -not -path "./.git/*" || true) + PY_FILES=$(find . -name "*.py" -not -path "./.git/*" \ + | grep -v salt | grep -v _states \ | grep -v _modules | grep -v pillar | grep -v venv \ | grep -v __pycache__ || true) + MAKE_FILES=$(find . \( -name "Makefile" -o -name "Makefile.*" -o -name "*.mk" \) \ + -not -path "./.git/*" -not -path "./.github/*" || true) + JAVA_FILES=$(find . \( -name "*.java" -o -name "*.kt" -o -name "*.kts" \) \ + -not -path "./.git/*" || true) + SWIFT_FILES=$(find . -name "*.swift" -not -path "./.git/*" || true) + DART_FILES=$(find . \( -name "*.dart" -o -name "pubspec.yaml" \) \ + -not -path "./.git/*" || true) + # V-lang detected by manifest (v.mod / vpkg.json); the .v extension + # collides with Verilog so we never key on it. + VMOD_FILES=$(find . \( -name "v.mod" -o -name "vpkg.json" \) \ + -not -path "./node_modules/*" -not -path "./.git/*" || true) + # ATS2 source extensions: rejected in favour of Idris2 / Rust/SPARK. + ATS2_FILES=$(find . \( -name "*.dats" -o -name "*.sats" -o -name "*.hats" \) \ + -not -path "./.git/*" || true) enforce "ReScript files" "use AffineScript instead" "$RES_FILES" enforce "Go files" "use Rust/WASM instead" "$GO_FILES" enforce "Python files" "only allowed for SaltStack" "$PY_FILES" + enforce "Makefiles" "use Mustfile/justfile instead" "$MAKE_FILES" + enforce "Java/Kotlin files" "use Rust/Tauri/Dioxus instead" "$JAVA_FILES" + enforce "Swift files" "use Tauri/Dioxus instead" "$SWIFT_FILES" + enforce "Flutter/Dart files" "use Tauri/Dioxus instead (Google lock-in)" "$DART_FILES" + enforce "V-lang manifests (v.mod / vpkg.json)" "V-lang is banned since 2026-04-10 — migrate to Zig" "$VMOD_FILES" + enforce "ATS2 files (.dats / .sats / .hats)" "use Idris2 or Rust/SPARK instead" "$ATS2_FILES" - name: Check for npm/bun artifacts # standards#67 — npm-avoidant: package-lock.json must never be tracked @@ -340,6 +361,15 @@ jobs: printf '%s\n' "$NPMRC_FILES" FAILED=1 fi + # Root package.json with runtime "dependencies" — moved here from + # the now-deleted language-policy.yml. devDependencies-only is + # tolerated for transitional tooling (e.g., a legacy bundler + # invoked from Justfile), but a `"dependencies"` field + # indicates Node.js runtime use, which Deno replaces. + if [ -f package.json ] && grep -q '"dependencies"[[:space:]]*:[[:space:]]*{[^}]' package.json; then + echo "❌ package.json with runtime \"dependencies\" detected. Use deno.json imports instead." + FAILED=1 + fi if [ -n "$FAILED" ]; then echo "See hyperpolymath/standards docs/JS-RUNTIME-POLICY.adoc for remediation." exit 1 diff --git a/.github/workflows/language-policy.yml b/.github/workflows/language-policy.yml deleted file mode 100644 index 23322113..00000000 --- a/.github/workflows/language-policy.yml +++ /dev/null @@ -1,189 +0,0 @@ -# SPDX-License-Identifier: PMPL-1.0-or-later -name: Language Policy Enforcement - -on: - push: - branches: [main, master] - pull_request: - branches: [main, master] - -# Estate guardrail: cancel superseded runs so re-pushes / rebased PR -# updates do not pile up queued runs against the shared account-wide -# Actions concurrency pool. Applied only to read-only check workflows -# (no publish/mutation), so cancelling a superseded run is always safe. -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - check-banned-languages: - name: Check for Banned Languages - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 - - # TypeScript check delegated to rsr-antipattern.yml (which honours the - # universal allowlist and the .claude/CLAUDE.md exemptions table). The - # blunt `find -name "*.ts"` form previously here false-positived on - # legitimate bridge files (e.g. tests/*.vitest.config.ts under the - # *vscode*/tests/ allowlist). - - - name: Check for ReScript files - run: | - # Estate policy: RS/TS/JS -> AffineScript -> typed-wasm. - # ReScript (.res) is no longer the TS replacement. - if find . -name "*.res" | grep -v node_modules | head -1 | grep -q .; then - echo "::error::ReScript files found. Use AffineScript instead." - find . -name "*.res" | grep -v node_modules - exit 1 - fi - echo "✓ No ReScript files found" - - - name: Check for Go files - run: | - if find . -name "*.go" | head -1 | grep -q .; then - echo "::error::Go files found. Use Rust instead." - find . -name "*.go" - exit 1 - fi - echo "✓ No Go files found" - - - name: Check for Python files (except Ansible) - run: | - # Allow Python only in ansible/ directories or for Ansible-specific files - PYTHON_FILES=$(find . -name "*.py" | grep -v __pycache__ | grep -v ".venv" | grep -v "ansible" | grep -v "molecule" || true) - if [ -n "$PYTHON_FILES" ]; then - echo "::error::Python files found outside Ansible context. Rewrite in Rust/AffineScript." - echo "$PYTHON_FILES" - exit 1 - fi - echo "✓ No unauthorized Python files found" - - - name: Check for Makefiles - run: | - MAKEFILES=$(find . -name "Makefile" -o -name "Makefile.*" -o -name "*.mk" | grep -v ".github" || true) - if [ -n "$MAKEFILES" ]; then - echo "::error::Makefiles found. Use Mustfile/justfile instead." - echo "$MAKEFILES" - exit 1 - fi - echo "✓ No Makefiles found" - - - name: Check for package.json (npm/node) - run: | - if [ -f "package.json" ]; then - # Allow if it only contains devDependencies for tooling - if grep -q '"dependencies"' package.json; then - echo "::error::package.json with runtime dependencies found. Use deno.json instead." - exit 1 - fi - fi - echo "✓ No npm runtime dependencies found" - - - name: Check for Java/Kotlin files - run: | - if find . -name "*.java" -o -name "*.kt" -o -name "*.kts" | head -1 | grep -q .; then - echo "::error::Java/Kotlin files found. Use Rust/Tauri/Dioxus instead." - find . -name "*.java" -o -name "*.kt" -o -name "*.kts" - exit 1 - fi - echo "✓ No Java/Kotlin files found" - - - name: Check for Swift files - run: | - if find . -name "*.swift" | head -1 | grep -q .; then - echo "::error::Swift files found. Use Tauri/Dioxus instead." - find . -name "*.swift" - exit 1 - fi - echo "✓ No Swift files found" - - - name: Check for V-lang code - run: | - # V-lang is banned as of 2026-04-10. Migration target: Zig. - # We detect by v.mod (V-lang's module manifest) rather than *.v - # extension because .v collides with Verilog hardware sources. - V_MOD_FILES=$(find . -name "v.mod" -not -path "*/node_modules/*" -not -path "*/.git/*" || true) - if [ -n "$V_MOD_FILES" ]; then - echo "::error::V-lang code found (detected via v.mod). V-lang is banned since 2026-04-10. Migrate to Zig." - echo "$V_MOD_FILES" - exit 1 - fi - # Also check for vpkg.json (alternative V package manifest) - VPKG_FILES=$(find . -name "vpkg.json" -not -path "*/node_modules/*" -not -path "*/.git/*" || true) - if [ -n "$VPKG_FILES" ]; then - echo "::error::V-lang code found (detected via vpkg.json). V-lang is banned since 2026-04-10. Migrate to Zig." - echo "$VPKG_FILES" - exit 1 - fi - echo "✓ No V-lang code found" - - - name: Check for Flutter/Dart files - run: | - if find . -name "*.dart" -o -name "pubspec.yaml" | head -1 | grep -q .; then - echo "::error::Flutter/Dart code found. Use Tauri/Dioxus instead (Google lock-in policy)." - find . -name "*.dart" -o -name "pubspec.yaml" - exit 1 - fi - echo "✓ No Flutter/Dart code found" - - - name: Check for ATS2 files - run: | - # ATS2 is rejected in favour of Idris2 (formal verification) and - # Rust/SPARK (safety-critical operational code). See LANGUAGE-POLICY.adoc §Amendments v1.1.0. - if find . -name "*.dats" -o -name "*.sats" -o -name "*.hats" | head -1 | grep -q .; then - echo "::error::ATS2 files found. Use Idris2 (formal verification) or Rust/SPARK (safety-critical code) instead." - find . -name "*.dats" -o -name "*.sats" -o -name "*.hats" - exit 1 - fi - echo "✓ No ATS2 files found" - - check-required-files: - name: Check Required Files - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 - - - name: Check for .machine_readable directory - run: | - if [ ! -d ".machine_readable" ]; then - echo "::warning::.machine_readable/ directory not found" - else - echo "✓ .machine_readable/ directory exists" - # Per estate policy: .a2ml is canonical; .scm is reserved for Guix. - for a2ml in STATE META ECOSYSTEM AGENTIC NEUROSYM PLAYBOOK; do - if [ ! -f ".machine_readable/6a2/${a2ml}.a2ml" ] && [ ! -f ".machine_readable/${a2ml}.a2ml" ]; then - echo "::warning::Missing .machine_readable/6a2/${a2ml}.a2ml (or top-level fallback)" - else - echo "✓ ${a2ml}.a2ml present" - fi - done - fi - - - name: Check for Mustfile/justfile - run: | - if [ ! -f "Mustfile" ] && [ ! -f "justfile" ]; then - echo "::warning::Neither Mustfile nor justfile found" - else - echo "✓ Build system files present" - fi - - - name: Check SPDX headers - run: | - MISSING_SPDX=0 - for ext in rs affine js jsx mjs ts tsx py go java kt swift sh bash; do - while IFS= read -r file; do - if [ -n "$file" ] && ! head -5 "$file" | grep -q "SPDX-License-Identifier"; then - echo "::warning::Missing SPDX header: $file" - MISSING_SPDX=$((MISSING_SPDX + 1)) - fi - done < <(find . -name "*.$ext" -type f 2>/dev/null | grep -v node_modules | grep -v .git | head -50) - done - if [ $MISSING_SPDX -gt 0 ]; then - echo "::warning::$MISSING_SPDX files missing SPDX headers" - fi