|
| 1 | +#!/usr/bin/env bash |
| 2 | +# SPDX-License-Identifier: MPL-2.0 |
| 3 | +# |
| 4 | +# run-mustfile.sh — EXECUTE the checks declared in a Mustfile.a2ml. |
| 5 | +# |
| 6 | +# The Mustfile declares hard repository invariants. Each '### <id>' block has a |
| 7 | +# severity and a means of discharge: |
| 8 | +# * `- run: <command>` — executable; run it, pass iff exit 0. |
| 9 | +# * `- verification: <text>`— governance / manually-verified; reported as |
| 10 | +# MANUAL (counted, never silently green). |
| 11 | +# |
| 12 | +# Blocking policy (fail loudly): a failing check of severity `critical` or |
| 13 | +# `high` fails the run (exit 1). A failing `warning`/lower check is advisory |
| 14 | +# (reported, non-blocking). This is the executable half of Mustfile checking; |
| 15 | +# scripts/check-mustfile-structure.sh is the structural half. |
| 16 | +# |
| 17 | +# Usage: run-mustfile.sh [path/to/Mustfile.a2ml] |
| 18 | +# Default: .machine_readable/contractiles/must/Mustfile.a2ml |
| 19 | +# Exit: 0 all blocking checks pass · 1 a blocking check failed · 2 file missing |
| 20 | + |
| 21 | +set -uo pipefail |
| 22 | + |
| 23 | +MUST="${1:-.machine_readable/contractiles/must/Mustfile.a2ml}" |
| 24 | +if [ ! -f "$MUST" ]; then |
| 25 | + echo "error: Mustfile not found: $MUST" >&2 |
| 26 | + exit 2 |
| 27 | +fi |
| 28 | + |
| 29 | +name="" sev="" cmd="" kind="" |
| 30 | +pass=0 warn=0 manual=0 blocking_fail=0 |
| 31 | + |
| 32 | +discharge() { |
| 33 | + [ -n "$name" ] || return 0 |
| 34 | + if [ "$kind" = "run" ]; then |
| 35 | + if bash -c "$cmd" >/dev/null 2>&1; then |
| 36 | + printf ' ✅ PASS [%-8s] %s\n' "$sev" "$name"; pass=$((pass + 1)) |
| 37 | + else |
| 38 | + case "$sev" in |
| 39 | + critical|high) |
| 40 | + printf ' ❌ FAIL [%-8s] %s\n' "$sev" "$name"; blocking_fail=$((blocking_fail + 1)) ;; |
| 41 | + *) |
| 42 | + printf ' ⚠️ WARN [%-8s] %s\n' "$sev" "$name"; warn=$((warn + 1)) ;; |
| 43 | + esac |
| 44 | + fi |
| 45 | + elif [ "$kind" = "verification" ]; then |
| 46 | + printf ' 🔎 MANUAL [%-8s] %s\n' "$sev" "$name"; manual=$((manual + 1)) |
| 47 | + else |
| 48 | + # A block with neither run nor verification is a structural defect; the |
| 49 | + # structural validator owns that, but flag it here too rather than ignore. |
| 50 | + printf ' ❓ NODISCHARGE [%-4s] %s\n' "$sev" "$name"; blocking_fail=$((blocking_fail + 1)) |
| 51 | + fi |
| 52 | +} |
| 53 | + |
| 54 | +# Trim leading whitespace so indented list items are still recognised. |
| 55 | +while IFS= read -r raw; do |
| 56 | + line="${raw#"${raw%%[![:space:]]*}"}" |
| 57 | + case "$line" in |
| 58 | + '### '*) discharge; name="${line:4}"; sev=""; cmd=""; kind="" ;; |
| 59 | + '- run: '*) cmd="${line#- run: }"; kind="run" ;; |
| 60 | + '- verification: '*) cmd="${line#- verification: }"; [ "$kind" = "run" ] || kind="verification" ;; |
| 61 | + '- severity: '*) sev="${line#- severity: }" ;; |
| 62 | + esac |
| 63 | +done < "$MUST" |
| 64 | +discharge # flush last block |
| 65 | + |
| 66 | +echo "" |
| 67 | +echo "Mustfile: $pass passed · $warn warning · $manual manual · $blocking_fail blocking-fail" |
| 68 | +if [ "$blocking_fail" -gt 0 ]; then |
| 69 | + echo "❌ Mustfile check FAILED ($blocking_fail blocking failure(s))" >&2 |
| 70 | + exit 1 |
| 71 | +fi |
| 72 | +echo "✅ Mustfile check passed (all critical/high checks green)" |
0 commit comments