Skip to content

Commit c3a62f0

Browse files
committed
refactor(governance): subsume language-policy.yml + add deno-ci-reusable
Foundational fix for the estate-template propagation bug class. Previously two parallel mechanisms enforced the language policy: * governance-reusable.yml's language-policy job (baseline-aware, modern) * language-policy.yml's check-banned-languages job (naive find-based) The second silently bypassed .hypatia-baseline.json, causing PR #167 to self-fail on a Python file already documented in the baseline. Per-repo copies of the same legacy workflow exhibited the same bug across the estate (absolute-zero, maa-framework, ...). This commit: * Moves the missing checks (Makefile / Java / Kotlin / Swift / Dart / pubspec / V-lang / ATS2) from language-policy.yml into the governance-reusable language-policy job so they all go through the same enforce() / is_exempt() helpers and honour .hypatia-baseline.json, .hypatia-ignore, and inline pragmas. * Adds the package.json-with-runtime-dependencies check (also from the legacy workflow) to the existing npm/bun step. * Deletes language-policy.yml. governance.yml (the thin wrapper) already invokes governance-reusable.yml @ workflow_call, so estate coverage is unchanged but the bug class becomes structurally impossible. * Adds deno-ci-reusable.yml, a new workflow_call reusable that replaces the broken per-repo rescript-deno-ci.yml template. The new reusable: - early-exits when no Deno targets / tests / config exist (was the `No test modules found` / `No target files found` failure mode); - declares top-level `permissions:` (was tripping workflow security linter); - drops the banned `npx rescript` step (ReScript was banned in new code 2026-04-30); - drops the vestigial security job that grep-audited permissions without doing anything with the result. Downstream rollout (separate PRs): each repo carrying rescript-deno-ci.yml or its own language-policy.yml replaces it with a thin wrapper: jobs: deno-ci: uses: hyperpolymath/standards/.github/workflows/deno-ci-reusable.yml@main
1 parent 91d8b88 commit c3a62f0

3 files changed

Lines changed: 151 additions & 193 deletions

File tree

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# SPDX-License-Identifier: PMPL-1.0-or-later
2+
# deno-ci-reusable.yml — Reusable Deno CI bundle (RSR).
3+
#
4+
# Replaces the per-repo `rescript-deno-ci.yml` template that copy-drifted
5+
# across the estate with several systemic bugs:
6+
#
7+
# * `deno test` ran unconditionally and failed `No test modules found`
8+
# on repos with no Deno tests.
9+
# * `deno lint` / `deno fmt --check` ran unconditionally and failed
10+
# `No target files found` on repos with no JS/TS targets.
11+
# * No top-level `permissions:` declaration, tripping the workflow
12+
# security linter in governance-reusable.yml.
13+
# * Still invoked `npx rescript` even though ReScript is banned in new
14+
# code as of 2026-04-30 (see standards/.claude/CLAUDE.md).
15+
# * The vestigial "security" job grep-audited for permission strings
16+
# without doing anything useful with the result.
17+
#
18+
# Each step now early-exits cleanly when the inputs don't exist, so the
19+
# job is a no-op on repos that don't yet ship Deno code but pass cleanly
20+
# when they start to.
21+
#
22+
# Caller example (single wrapper, mirrors governance.yml's pattern):
23+
#
24+
# jobs:
25+
# deno-ci:
26+
# uses: hyperpolymath/standards/.github/workflows/deno-ci-reusable.yml@main
27+
28+
name: Deno CI (reusable)
29+
30+
on:
31+
workflow_call:
32+
inputs:
33+
runs-on:
34+
description: Runner label for the deno-ci job
35+
type: string
36+
required: false
37+
default: ubuntu-latest
38+
deno-version:
39+
description: Deno version selector
40+
type: string
41+
required: false
42+
default: v2.x
43+
44+
permissions:
45+
contents: read
46+
47+
jobs:
48+
deno-ci:
49+
name: Deno CI
50+
runs-on: ${{ inputs.runs-on }}
51+
permissions:
52+
contents: read
53+
steps:
54+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
55+
with:
56+
repository: ${{ github.repository }}
57+
ref: ${{ github.ref }}
58+
59+
- uses: denoland/setup-deno@e95548e56dfa95d4e1a28d6f422fafe75c4c26fb # v2.0.3
60+
with:
61+
deno-version: ${{ inputs.deno-version }}
62+
63+
- name: Detect Deno targets
64+
id: detect
65+
run: |
66+
# `deno.json` (or its legacy `deno.jsonc`) is the canonical
67+
# signal that this repo opts into Deno tooling. We also count
68+
# raw .ts/.js files as a fallback for repos still mid-migration
69+
# to deno.json. Either way, the per-step guards below honour
70+
# any `include` / `exclude` scoping the consumer declared in
71+
# deno.json.
72+
has_config=false
73+
has_targets=false
74+
has_tests=false
75+
if [ -f deno.json ] || [ -f deno.jsonc ]; then has_config=true; fi
76+
if find . -path ./node_modules -prune -o -path ./.git -prune \
77+
-o \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" -o -name "*.mjs" \) \
78+
-type f -print 2>/dev/null | head -1 | grep -q .; then
79+
has_targets=true
80+
fi
81+
if find . -path ./node_modules -prune -o -path ./.git -prune \
82+
-o \( -name "*_test.ts" -o -name "*.test.ts" -o -name "*_test.js" -o -name "*.test.js" \) \
83+
-type f -print 2>/dev/null | head -1 | grep -q .; then
84+
has_tests=true
85+
fi
86+
echo "has_config=$has_config" >> "$GITHUB_OUTPUT"
87+
echo "has_targets=$has_targets" >> "$GITHUB_OUTPUT"
88+
echo "has_tests=$has_tests" >> "$GITHUB_OUTPUT"
89+
echo "deno-ci: config=$has_config targets=$has_targets tests=$has_tests"
90+
91+
- name: Deno lint
92+
if: steps.detect.outputs.has_targets == 'true' || steps.detect.outputs.has_config == 'true'
93+
run: deno lint
94+
95+
- name: Deno fmt check
96+
if: steps.detect.outputs.has_targets == 'true' || steps.detect.outputs.has_config == 'true'
97+
run: deno fmt --check
98+
99+
- name: Deno test
100+
if: steps.detect.outputs.has_tests == 'true'
101+
run: deno test --allow-all --coverage=coverage
102+
103+
- name: Deno type check
104+
if: steps.detect.outputs.has_targets == 'true'
105+
# Soft-pass: `deno check` exits non-zero on unresolved imports we
106+
# don't yet require contributors to vendor. We surface output for
107+
# diagnostics but don't fail the gate.
108+
run: deno check . || echo "::warning::deno check reported issues (non-blocking)."
109+
110+
- name: Summary
111+
run: |
112+
if [ "${{ steps.detect.outputs.has_config }}" = "false" ] \
113+
&& [ "${{ steps.detect.outputs.has_targets }}" = "false" ]; then
114+
echo "deno-ci: no Deno targets detected — skipped all checks."
115+
else
116+
echo "deno-ci: passed (lint / fmt / type / test where applicable)."
117+
fi

.github/workflows/governance-reusable.yml

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ jobs:
236236
# 3. Inline `# hypatia:ignore ...` pragma in the file's first
237237
# 8 lines — the same escape the Hypatia scanner itself
238238
# honours.
239-
- name: Check for ReScript / Go / Python (banned language files)
239+
- name: Check banned-language files (ReScript / Go / Python / Java / Kotlin / Swift / Dart / V-lang / ATS2 / Makefile)
240240
run: |
241241
rule_module="cicd_rules"
242242
rule_type="banned_language_file"
@@ -300,15 +300,36 @@ jobs:
300300
echo "✅ No non-exempt ${label}"
301301
}
302302
303-
RES_FILES=$(find . -name "*.res" | grep -v node_modules || true)
304-
GO_FILES=$(find . -name "*.go" || true)
305-
PY_FILES=$(find . -name "*.py" | grep -v salt | grep -v _states \
303+
RES_FILES=$(find . -name "*.res" -not -path "./node_modules/*" -not -path "./.git/*" || true)
304+
GO_FILES=$(find . -name "*.go" -not -path "./.git/*" || true)
305+
PY_FILES=$(find . -name "*.py" -not -path "./.git/*" \
306+
| grep -v salt | grep -v _states \
306307
| grep -v _modules | grep -v pillar | grep -v venv \
307308
| grep -v __pycache__ || true)
309+
MAKE_FILES=$(find . \( -name "Makefile" -o -name "Makefile.*" -o -name "*.mk" \) \
310+
-not -path "./.git/*" -not -path "./.github/*" || true)
311+
JAVA_FILES=$(find . \( -name "*.java" -o -name "*.kt" -o -name "*.kts" \) \
312+
-not -path "./.git/*" || true)
313+
SWIFT_FILES=$(find . -name "*.swift" -not -path "./.git/*" || true)
314+
DART_FILES=$(find . \( -name "*.dart" -o -name "pubspec.yaml" \) \
315+
-not -path "./.git/*" || true)
316+
# V-lang detected by manifest (v.mod / vpkg.json); the .v extension
317+
# collides with Verilog so we never key on it.
318+
VMOD_FILES=$(find . \( -name "v.mod" -o -name "vpkg.json" \) \
319+
-not -path "./node_modules/*" -not -path "./.git/*" || true)
320+
# ATS2 source extensions: rejected in favour of Idris2 / Rust/SPARK.
321+
ATS2_FILES=$(find . \( -name "*.dats" -o -name "*.sats" -o -name "*.hats" \) \
322+
-not -path "./.git/*" || true)
308323
309324
enforce "ReScript files" "use AffineScript instead" "$RES_FILES"
310325
enforce "Go files" "use Rust/WASM instead" "$GO_FILES"
311326
enforce "Python files" "only allowed for SaltStack" "$PY_FILES"
327+
enforce "Makefiles" "use Mustfile/justfile instead" "$MAKE_FILES"
328+
enforce "Java/Kotlin files" "use Rust/Tauri/Dioxus instead" "$JAVA_FILES"
329+
enforce "Swift files" "use Tauri/Dioxus instead" "$SWIFT_FILES"
330+
enforce "Flutter/Dart files" "use Tauri/Dioxus instead (Google lock-in)" "$DART_FILES"
331+
enforce "V-lang manifests (v.mod / vpkg.json)" "V-lang is banned since 2026-04-10 — migrate to Zig" "$VMOD_FILES"
332+
enforce "ATS2 files (.dats / .sats / .hats)" "use Idris2 or Rust/SPARK instead" "$ATS2_FILES"
312333
313334
- name: Check for npm/bun artifacts
314335
# standards#67 — npm-avoidant: package-lock.json must never be tracked
@@ -340,6 +361,15 @@ jobs:
340361
printf '%s\n' "$NPMRC_FILES"
341362
FAILED=1
342363
fi
364+
# Root package.json with runtime "dependencies" — moved here from
365+
# the now-deleted language-policy.yml. devDependencies-only is
366+
# tolerated for transitional tooling (e.g., a legacy bundler
367+
# invoked from Justfile), but a `"dependencies"` field
368+
# indicates Node.js runtime use, which Deno replaces.
369+
if [ -f package.json ] && grep -q '"dependencies"[[:space:]]*:[[:space:]]*{[^}]' package.json; then
370+
echo "❌ package.json with runtime \"dependencies\" detected. Use deno.json imports instead."
371+
FAILED=1
372+
fi
343373
if [ -n "$FAILED" ]; then
344374
echo "See hyperpolymath/standards docs/JS-RUNTIME-POLICY.adoc for remediation."
345375
exit 1

.github/workflows/language-policy.yml

Lines changed: 0 additions & 189 deletions
This file was deleted.

0 commit comments

Comments
 (0)