|
| 1 | +#!/usr/bin/env bash |
| 2 | +# SPDX-License-Identifier: MPL-2.0 |
| 3 | +# Copyright (c) 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk> |
| 4 | +# |
| 5 | +# Pre-commit hook: a change to the RSR rule table is checked against the estate. |
| 6 | +# |
| 7 | +# The rule table in src/rsr.rs is the source of truth for RSR compliance, and it |
| 8 | +# has rotted twice before: once by demanding LICENSE.txt (0 of 269 repositories |
| 9 | +# carried it) and once by holding the .well-known/security.txt checks advisory |
| 10 | +# after the migration they were waiting on had finished. |
| 11 | +# |
| 12 | +# This hook runs scripts/detect-rsr-drift.sh whenever src/rsr.rs is part of the |
| 13 | +# change, so that a rule-table edit cannot land without being measured against |
| 14 | +# the repositories it will judge. |
| 15 | +# |
| 16 | +# The estate census is cached, not fetched here: a 269-repository crawl has no |
| 17 | +# business inside a commit. Refresh it first with: |
| 18 | +# |
| 19 | +# just refresh-rsr-census |
| 20 | +# |
| 21 | +# If no census is present the hook still validates the table itself (it parses, |
| 22 | +# its severity tuples are well-formed, its migration posture is coherent) and |
| 23 | +# says loudly that the estate comparison was skipped. It does not fail the |
| 24 | +# commit for a missing census; it does fail it for actual drift. |
| 25 | + |
| 26 | +set -euo pipefail |
| 27 | + |
| 28 | +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" |
| 29 | +DETECTOR="$REPO_ROOT/scripts/detect-rsr-drift.sh" |
| 30 | +RULE_TABLE="bots/rhodibot/src/rsr.rs" |
| 31 | +CENSUS="${RSR_CENSUS_DIR:-$REPO_ROOT/.rsr-census}" |
| 32 | + |
| 33 | +cd "$REPO_ROOT" |
| 34 | + |
| 35 | +if [ ! -x "$DETECTOR" ]; then |
| 36 | + echo "ERROR: detector not found or not executable: $DETECTOR" >&2 |
| 37 | + exit 1 |
| 38 | +fi |
| 39 | + |
| 40 | +# Only run when the rule table is actually part of this change. |
| 41 | +staged=$(git diff --cached --name-only 2>/dev/null || true) |
| 42 | +if ! printf '%s\n' "$staged" | grep -qxF "$RULE_TABLE"; then |
| 43 | + # Also run when the detector itself is being changed, since its verdicts are |
| 44 | + # only as good as its parser. |
| 45 | + if ! printf '%s\n' "$staged" | grep -qxF "scripts/detect-rsr-drift.sh"; then |
| 46 | + exit 0 |
| 47 | + fi |
| 48 | +fi |
| 49 | + |
| 50 | +echo "=== RSR rule-table drift check ===" |
| 51 | + |
| 52 | +if [ -d "$CENSUS" ] && [ -n "$(ls -A "$CENSUS" 2>/dev/null)" ]; then |
| 53 | + repos=$(ls -1 "$CENSUS" | wc -l | tr -d ' ') |
| 54 | + echo "using cached census: $CENSUS ($repos repositories)" |
| 55 | + echo " (age: $(find "$CENSUS" -maxdepth 1 -type f -printf '%TY-%Tm-%Td\n' 2>/dev/null | sort | head -1))" |
| 56 | + set +e |
| 57 | + "$DETECTOR" --source "$RULE_TABLE" --trees-dir "$CENSUS" |
| 58 | + rc=$? |
| 59 | + set -e |
| 60 | +else |
| 61 | + echo "no census at $CENSUS — validating the table only" |
| 62 | + echo "the estate comparison is SKIPPED; run: just refresh-rsr-census" |
| 63 | + set +e |
| 64 | + "$DETECTOR" --source "$RULE_TABLE" |
| 65 | + rc=$? |
| 66 | + set -e |
| 67 | + # Exit 1 from a source-only run means the table itself is broken, which |
| 68 | + # must block. Exit 2 means indeterminate. |
| 69 | +fi |
| 70 | + |
| 71 | +case "$rc" in |
| 72 | + 0) echo "PASS: no rule-table drift" ;; |
| 73 | + 1) echo "FAIL: rule-table drift found — see the report above" >&2; exit 1 ;; |
| 74 | + 2) echo "WARN: drift check indeterminate (some inputs unavailable)" >&2; exit 0 ;; |
| 75 | + *) echo "FAIL: drift check errored (rc=$rc)" >&2; exit 1 ;; |
| 76 | +esac |
| 77 | + |
| 78 | +exit 0 |
0 commit comments