Skip to content

Commit 55f5cbd

Browse files
committed
feat(wave1): make the declared automation actually run
The estate declared mandatory checks and install-only hooks that nothing executed. Wave 1 wires them so the enforcement is real, not aspirational. - scripts/run-mustfile.sh: EXECUTE the Mustfile's `- run:` invariants (16 checks that were declared but never run by any CI job). Critical/high failures block; warning-severity failures are advisory; `- verification:` checks are reported as MANUAL (counted, never silently green). Wired into boj-build.yml's contractile job as a real "Mustfile Enforcement" step (the repo passes all 16 today, so it gates honestly). Justfile: must-check. - hooks/install.sh + Justfile hooks-install: install the pre-commit guard (language policy + registry-drift) into .git/hooks. Previously the hooks only ran if a contributor manually copied them; now `just hooks-install` wires a thin shim that execs the tracked hook (single source of truth). - registry-verify.yml: on drift, write the exact remediation (`just registry` + `git add` + `just hooks-install`) to the CI job summary instead of a bare exit 1 — this is the recurring standards#381 pain made self-explaining. - hypatia-scan-reusable.yml: the "critical issues" step is honestly labelled ADVISORY / does-not-gate (echo + job summary) so a green check carrying critical findings is not mistaken for zero findings. Blocking promotion tracked at standards#399/#437. (no-js-scan.yml was already honest.) - just doctor: report whether the optional contractile.just import resolved and whether the pre-commit hook is installed (import? fails silent otherwise). - scripts/tests/wave1-automation-test.sh (9 assertions) + Justfile automation-test: prove the Mustfile runner blocks on critical/high, stays advisory on warning, and the hook installer is idempotent. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0114ps6mY5jAH4SzbGxeuYjc
1 parent d9a1d74 commit 55f5cbd

7 files changed

Lines changed: 244 additions & 7 deletions

File tree

.github/workflows/boj-build.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,18 @@ jobs:
7171
# Real structural validation (no placeholder): every Mustfile check
7272
# must carry a severity and a means of discharge (run or verification).
7373
# A hollow check fails loudly. See scripts/check-mustfile-structure.sh
74-
# (unit-tested by scripts/tests/wave0-false-green-test.sh). Executable
75-
# invocation of `- run:` checks arrives with the Wave-1 must-verify job.
74+
# (unit-tested by scripts/tests/wave0-false-green-test.sh).
7675
bash scripts/check-mustfile-structure.sh
7776
77+
- name: Mustfile Enforcement (execute the checks)
78+
run: |
79+
set -euo pipefail
80+
# Execute the Mustfile's `- run:` invariants — the mandatory checks
81+
# that were previously declared but never run by any CI job. A failing
82+
# critical/high check fails the build (fail loudly); warning-severity
83+
# failures are reported but non-blocking. See scripts/run-mustfile.sh.
84+
bash scripts/run-mustfile.sh
85+
7886
- name: Contractile Check
7987
run: |
8088
set -euo pipefail

.github/workflows/hypatia-scan-reusable.yml

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,20 @@ jobs:
132132
panic-attack-findings.json
133133
retention-days: 90
134134

135-
- name: Check for critical issues
135+
- name: Check for critical issues (ADVISORY — does not gate)
136136
if: steps.scan.outputs.critical > 0
137137
run: |
138-
echo "Critical issues found!"
139-
echo "Review hypatia-findings.json for details."
140-
# Warn but don't fail — fix forward
138+
# This scan is ADVISORY / fix-forward: it never fails the build. The
139+
# label and summary state that explicitly so a green check carrying
140+
# critical findings is not mistaken for "no critical findings".
141+
# (Promotion to blocking is tracked with the baseline reconciliation,
142+
# standards#399/#437.)
143+
echo "::warning title=Hypatia (ADVISORY, non-blocking)::${{ steps.scan.outputs.critical }} critical finding(s). This scan does not gate — review hypatia-findings.json and fix forward."
144+
{
145+
echo "### Hypatia scan — ADVISORY (does not gate)"
146+
echo ""
147+
echo "**${{ steps.scan.outputs.critical }} critical finding(s)** reported."
148+
echo "This scan is fix-forward and **never fails the build**; a green"
149+
echo "check here does not mean zero critical findings. See the"
150+
echo "\`hypatia-scan-findings\` artifact. Blocking promotion: standards#399/#437."
151+
} >> "$GITHUB_STEP_SUMMARY"

.github/workflows/registry-verify.yml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,23 @@ jobs:
3131

3232
- name: Verify registry + derived topology are current
3333
run: |
34-
bash scripts/build-registry.sh --check
34+
if ! bash scripts/build-registry.sh --check; then
35+
{
36+
echo "### Registry drift detected"
37+
echo ""
38+
echo "A tracked file under a spec home (or STATE.a2ml) changed without"
39+
echo "regenerating the derived registry/topology. Fix locally:"
40+
echo ""
41+
echo '```sh'
42+
echo "just registry # or: bash scripts/build-registry.sh"
43+
echo "git add .machine_readable/REGISTRY.a2ml TOPOLOGY.md"
44+
echo '```'
45+
echo ""
46+
echo "Install the pre-commit guard so this is caught before push:"
47+
echo ""
48+
echo '```sh'
49+
echo "just hooks-install"
50+
echo '```'
51+
} >> "$GITHUB_STEP_SUMMARY"
52+
exit 1
53+
fi

Justfile

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,22 @@ staleness-test:
4545
false-green-test:
4646
@bash scripts/tests/wave0-false-green-test.sh
4747

48+
# Wave-1 automation regression: Mustfile runner + hook installer
49+
automation-test:
50+
@bash scripts/tests/wave1-automation-test.sh
51+
4852
# Structural validation of the Mustfile contract (severity + run/verification per check)
4953
mustfile-check path=".machine_readable/contractiles/must/Mustfile.a2ml":
5054
@bash scripts/check-mustfile-structure.sh "{{path}}"
5155

56+
# Execute the Mustfile's `- run:` checks (critical/high failures block)
57+
must-check path=".machine_readable/contractiles/must/Mustfile.a2ml":
58+
@bash scripts/run-mustfile.sh "{{path}}"
59+
60+
# Install this repo's git hooks into .git/hooks/ (pre-commit guards)
61+
hooks-install:
62+
@bash hooks/install.sh
63+
5264
# Aggregate compliance gate: registry drift is the HARD gate (registry-check,
5365
# a hard dep). The RSR self-audit is INFORMATIONAL — a monorepo is not expected
5466
# to score Gold — but a *broken* audit (exit 4 / unexpected) must fail loudly
@@ -139,6 +151,10 @@ doctor:
139151
@command -v git >/dev/null 2>&1 && echo " [OK] git" || echo " [FAIL] git not found"
140152
@echo "Checking for hardcoded paths..."
141153
@grep -rn '$HOME\|$ECLIPSE_DIR' --include='*.rs' --include='*.ex' --include='*.res' --include='*.gleam' --include='*.sh' . 2>/dev/null | head -5 || echo " [OK] No hardcoded paths"
154+
@echo "Checking optional imports (import? does not fail when absent — report it)..."
155+
@test -f contractile.just && echo " [OK] contractile.just present (import resolved)" || echo " [INFO] contractile.just absent — its recipes are unavailable (needs the external 'contractile' generator)"
156+
@echo "Checking git hooks are installed..."
157+
@test -f "$(git rev-parse --git-dir)/hooks/pre-commit" && echo " [OK] pre-commit hook installed" || echo " [INFO] pre-commit hook not installed — run 'just hooks-install'"
142158
@echo "Diagnostics complete."
143159

144160
# Auto-repair common issues

hooks/install.sh

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#!/usr/bin/env bash
2+
# SPDX-License-Identifier: MPL-2.0
3+
#
4+
# install.sh — install this repo's git hooks into .git/hooks/.
5+
#
6+
# The hooks in hooks/ (pre-commit language-policy + registry-drift guard, SPDX,
7+
# SHA-pin and permission validators) only run if they are present in
8+
# .git/hooks/. Nothing installed them automatically, so their guards were
9+
# effectively optional (a contributor had to know to `cp` them). This installer
10+
# wires them once. Idempotent; safe to re-run.
11+
#
12+
# Usage: bash hooks/install.sh (install / refresh)
13+
# just hooks-install (same, via recipe)
14+
#
15+
# It installs a thin .git/hooks/pre-commit that execs hooks/pre-commit from the
16+
# work tree, so the tracked hook stays the single source of truth and updates
17+
# take effect without re-installing.
18+
19+
set -euo pipefail
20+
21+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
22+
GITDIR="$(git -C "$ROOT" rev-parse --git-dir 2>/dev/null || true)"
23+
if [ -z "$GITDIR" ]; then
24+
echo "error: not a git repository: $ROOT" >&2
25+
exit 1
26+
fi
27+
# Normalise to an absolute path (git-dir may be relative to ROOT).
28+
case "$GITDIR" in
29+
/*) : ;;
30+
*) GITDIR="$ROOT/$GITDIR" ;;
31+
esac
32+
33+
HOOKDIR="$GITDIR/hooks"
34+
mkdir -p "$HOOKDIR"
35+
36+
installed=0
37+
for src in "$ROOT"/hooks/pre-commit; do
38+
[ -f "$src" ] || continue
39+
name="$(basename "$src")"
40+
dst="$HOOKDIR/$name"
41+
cat > "$dst" <<EOF
42+
#!/usr/bin/env bash
43+
# SPDX-License-Identifier: MPL-2.0
44+
# Auto-installed by hooks/install.sh — execs the tracked hook so the work-tree
45+
# copy stays the single source of truth. Do not edit; edit hooks/$name instead.
46+
exec "\$(git rev-parse --show-toplevel)/hooks/$name" "\$@"
47+
EOF
48+
chmod +x "$dst"
49+
echo "installed: $dst -> hooks/$name"
50+
installed=$((installed + 1))
51+
done
52+
53+
if [ "$installed" -eq 0 ]; then
54+
echo "error: no hooks found to install under $ROOT/hooks/" >&2
55+
exit 1
56+
fi
57+
echo "$installed hook(s) installed into $HOOKDIR"

scripts/run-mustfile.sh

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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)"
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#!/usr/bin/env bash
2+
# SPDX-License-Identifier: MPL-2.0
3+
set -uo pipefail
4+
#
5+
# Wave-1 "make the automation actually run" regression test.
6+
#
7+
# Covers the runners that Wave 1 wired into CI/hooks so they can no longer be
8+
# declared-but-never-run:
9+
# * scripts/run-mustfile.sh — executes the Mustfile invariants (real gate)
10+
# * hooks/install.sh — installs the pre-commit guard into .git/hooks
11+
12+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
13+
TMP="$(mktemp -d)"
14+
trap 'rm -rf "$TMP"' EXIT
15+
16+
pass=0 fail=0
17+
ok() { echo "$1"; pass=$((pass + 1)); }
18+
bad() { echo "$1"; fail=$((fail + 1)); }
19+
expect() { if [ "$2" -eq "$1" ]; then ok "$3 (exit $2)"; else bad "$3 (wanted $1, got $2)"; fi; }
20+
21+
echo "== run-mustfile.sh =="
22+
RM="$ROOT/scripts/run-mustfile.sh"
23+
# The repo's real Mustfile must pass (all critical/high green) — this is the
24+
# ground truth that lets the CI job block honestly.
25+
bash "$RM" "$ROOT/.machine_readable/contractiles/must/Mustfile.a2ml" >/dev/null 2>&1
26+
expect 0 $? "real Mustfile passes enforcement"
27+
# A failing critical check blocks.
28+
cf="$TMP/crit.a2ml"; printf '### x\n- run: test -f /nonexistent-xyzzy\n- severity: critical\n' > "$cf"
29+
bash "$RM" "$cf" >/dev/null 2>&1; expect 1 $? "failing critical check blocks"
30+
# A failing high check blocks.
31+
hf="$TMP/high.a2ml"; printf '### x\n- run: test -f /nonexistent-xyzzy\n- severity: high\n' > "$hf"
32+
bash "$RM" "$hf" >/dev/null 2>&1; expect 1 $? "failing high check blocks"
33+
# A failing warning check is advisory (non-blocking).
34+
wf="$TMP/warn.a2ml"; printf '### x\n- run: test -f /nonexistent-xyzzy\n- severity: warning\n' > "$wf"
35+
bash "$RM" "$wf" >/dev/null 2>&1; expect 0 $? "failing warning check is non-blocking"
36+
# Missing file errors.
37+
bash "$RM" "$TMP/nope.a2ml" >/dev/null 2>&1; expect 2 $? "missing Mustfile errors"
38+
39+
echo "== hooks/install.sh =="
40+
HR="$TMP/hookrepo"; mkdir -p "$HR/hooks"; git -C "$HR" init -q
41+
cp "$ROOT/hooks/pre-commit" "$HR/hooks/"; cp "$ROOT/hooks/install.sh" "$HR/hooks/"
42+
( cd "$HR" && bash hooks/install.sh >/dev/null 2>&1 ); expect 0 $? "installer runs"
43+
if [ -x "$HR/.git/hooks/pre-commit" ]; then ok "pre-commit installed + executable"; else bad "pre-commit not installed"; fi
44+
if grep -q 'exec .*hooks/pre-commit' "$HR/.git/hooks/pre-commit" 2>/dev/null; then
45+
ok "installed hook execs the tracked work-tree copy (single source of truth)"
46+
else
47+
bad "installed hook does not delegate to tracked copy"
48+
fi
49+
# Idempotent: re-running is safe.
50+
( cd "$HR" && bash hooks/install.sh >/dev/null 2>&1 ); expect 0 $? "installer is idempotent"
51+
52+
echo
53+
echo "Wave-1 automation regression: $pass passed, $fail failed"
54+
[ "$fail" -eq 0 ]

0 commit comments

Comments
 (0)