Skip to content

Commit d1b809a

Browse files
fix(governance): honour .hypatia-ignore / inline pragma in banned-language checks (#120)
## Root cause The shared `governance-reusable.yml` `Language / package anti-pattern policy` job enforces the TypeScript ban with a full exemption mechanism, but the **ReScript / Go / Python** checks were crude `find` one-liners with **no escape**. They ignored the estate's declared machine-readable exemption — `.hypatia-ignore` rule `cicd_rules/banned_language_file` and the inline `# hypatia:ignore … cicd_rules/banned_language_file` pragma — even though the Hypatia scanner itself honours it. Per the **Explicit-Escape Principle** (Refs standards#72): re-emergence of a banned default despite a declared, tool-honoured escape is a *tooling defect*, not a policy violation. ## Symptom this fixes `hyperpolymath/hypatia#270` (a 1-line CI pin) and transitively **#271** are `BLOCKED` because two intentionally-exempted criterion bench scripts (`scripts/check-bench-regression.py`, `scripts/update-bench-baselines.py` — carrying the inline pragma, listed in `.hypatia-ignore`, documented in `.hypatia-exemptions.md`, used by `tests.yml`) trip this shared gate. The defect is estate-wide; fixing it here resolves every consumer of the shared bundle. ## Change Replaces the three crude checks with a single exemption-aware step. A file is exempt from `cicd_rules/banned_language_file` iff it is listed in `.hypatia-ignore` for that rule **or** carries the inline pragma in its first 8 lines. ## Verification (dry-run, `bash -eo pipefail`) - Only-exempt files present → **PASS** (the #270/#271 case) - A non-exempt `.py` added → **FAIL** (enforcement preserved) - Clean repo, zero banned files → **PASS** (no `grep -v`/pipefail false-fail; the old behaviour was preserved via `|| true`) Refs standards#72 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 9b23627 commit d1b809a

1 file changed

Lines changed: 54 additions & 25 deletions

File tree

.github/workflows/governance-reusable.yml

Lines changed: 54 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -134,34 +134,63 @@ jobs:
134134
print(f"✅ No TypeScript files outside allowlist ({len(exemption_patterns)} per-repo exemption(s) parsed).")
135135
PYEOF
136136
137-
- name: Check for ReScript
137+
# Shared escape hatch for the banned-language-file checks below.
138+
# Honours the estate's declared machine-readable exemption (standards#72,
139+
# Explicit-Escape Principle): a file is exempt from the
140+
# `cicd_rules/banned_language_file` rule if EITHER
141+
# * `.hypatia-ignore` contains the exact line
142+
# `cicd_rules/banned_language_file:<relpath>`, OR
143+
# * the file carries an inline `# hypatia:ignore ...
144+
# cicd_rules/banned_language_file` pragma in its first 8 lines
145+
# — the same escape the Hypatia scanner itself honours.
146+
- name: Check for ReScript / Go / Python (banned language files)
138147
run: |
139-
RES_FILES=$(find . -name "*.res" | grep -v node_modules || true)
140-
if [ -n "$RES_FILES" ]; then
141-
echo "❌ ReScript files detected - use AffineScript instead"
142-
echo "$RES_FILES"
143-
exit 1
144-
fi
145-
echo "✅ No ReScript files"
148+
rule="cicd_rules/banned_language_file"
146149
147-
- name: Check for Go
148-
run: |
149-
if find . -name "*.go" | grep -q .; then
150-
echo "❌ Go files detected - use Rust/WASM instead"
151-
find . -name "*.go"
152-
exit 1
153-
fi
154-
echo "✅ No Go files"
150+
is_exempt() {
151+
f="${1#./}"
152+
if [ -f .hypatia-ignore ] && grep -qxF "${rule}:${f}" .hypatia-ignore; then
153+
return 0
154+
fi
155+
if head -n 8 "$1" 2>/dev/null | grep -q "hypatia:ignore.*${rule}"; then
156+
return 0
157+
fi
158+
return 1
159+
}
155160
156-
- name: Check for Python (non-SaltStack)
157-
run: |
158-
PY_FILES=$(find . -name "*.py" | grep -v salt | grep -v _states | grep -v _modules | grep -v pillar | grep -v venv | grep -v __pycache__ || true)
159-
if [ -n "$PY_FILES" ]; then
160-
echo "❌ Python files detected - only allowed for SaltStack"
161-
echo "$PY_FILES"
162-
exit 1
163-
fi
164-
echo "✅ No non-SaltStack Python files"
161+
# $1 = human label, $2 = remediation hint, $3 = newline-separated
162+
# candidate paths (possibly empty). Reads via here-string so this
163+
# runs in the current shell — no subshell, exit propagates.
164+
enforce() {
165+
label="$1"; hint="$2"; candidates="$3"; violations=""
166+
while IFS= read -r f; do
167+
[ -z "$f" ] && continue
168+
if is_exempt "$f"; then
169+
echo "⏭️ exempt (${rule}): $f"
170+
continue
171+
fi
172+
violations="${violations}${f}
173+
"
174+
done <<< "$candidates"
175+
if [ -n "$(printf '%s' "$violations" | tr -d '[:space:]')" ]; then
176+
echo "❌ ${label} detected - ${hint}"
177+
printf '%s' "$violations"
178+
echo " (declare an exemption via .hypatia-ignore or an inline"
179+
echo " '# hypatia:ignore ${rule}' pragma if intentional)"
180+
exit 1
181+
fi
182+
echo "✅ No non-exempt ${label}"
183+
}
184+
185+
RES_FILES=$(find . -name "*.res" | grep -v node_modules || true)
186+
GO_FILES=$(find . -name "*.go" || true)
187+
PY_FILES=$(find . -name "*.py" | grep -v salt | grep -v _states \
188+
| grep -v _modules | grep -v pillar | grep -v venv \
189+
| grep -v __pycache__ || true)
190+
191+
enforce "ReScript files" "use AffineScript instead" "$RES_FILES"
192+
enforce "Go files" "use Rust/WASM instead" "$GO_FILES"
193+
enforce "Python files" "only allowed for SaltStack" "$PY_FILES"
165194
166195
- name: Check for npm/bun artifacts
167196
run: |

0 commit comments

Comments
 (0)