yak(sobelow-in-the-lens-pass-vr40): sobelow in the Lens pass
This commit is contained in:
Executable
+138
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env bash
|
||||
# check-lens-sensors.sh - pin the Lens sensor wiring that must not drift.
|
||||
#
|
||||
# Why this exists: `make verify` echoes VERIFY_SENSORS and runs each name from
|
||||
# the app's sensor loop. A sensor missing from that list is never run, and a
|
||||
# sensor target that can fall back (or skip the tool when it is absent) exits 0
|
||||
# when it did not check anything - in both cases the pass reports a check it
|
||||
# did not run. AGENTS.md: "A gate that reports a check it did not run is worse
|
||||
# than no gate."
|
||||
#
|
||||
# This script is static and grep-only: no mise, no elixir, no network, no
|
||||
# database. It pins load-bearing text (the VERIFY_SENSORS line and the sobelow
|
||||
# recipe) and the rationale rule for per-finding skips. Renaming the target or
|
||||
# the variable means updating this script in the same commit - that is the
|
||||
# point: the wiring stays a reviewed decision rather than an accident.
|
||||
#
|
||||
# Run standalone, or via root `make check`.
|
||||
set -euo pipefail
|
||||
|
||||
root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||
app_makefile="$root/app/Makefile"
|
||||
skips="$root/app/.sobelow-skips"
|
||||
status=0
|
||||
|
||||
# Verified 2026-10-28 against sobelow 0.17.0:
|
||||
# `cd app && mix sobelow` with 3 findings -> exit 0 (findings alone are NOT
|
||||
# fatal); `cd app && mix sobelow --exit` -> exit 1. `--exit` defaults to the
|
||||
# `low` confidence threshold, which is what we want: every finding counts.
|
||||
SOBELOW_FAIL_FLAG='--exit'
|
||||
|
||||
fail() { echo "FAIL: $*" >&2; status=1; }
|
||||
|
||||
# --- T1: sobelow is wired and listed first in VERIFY_SENSORS ---------------
|
||||
# `make verify` echoes this list, so a missing name means the pass reports a
|
||||
# sensor it never runs. Cheapest-first ordering is part of the contract.
|
||||
check_verify_sensors() {
|
||||
local line sensors i
|
||||
line=$(grep -E '^VERIFY_SENSORS[[:space:]]*:=' "$app_makefile" || true)
|
||||
if [ -z "$line" ]; then
|
||||
fail "app/Makefile has no VERIFY_SENSORS := line"
|
||||
return
|
||||
fi
|
||||
|
||||
sensors=$(printf '%s' "${line#*:=}" | tr -s '[:space:]' ' ')
|
||||
local -a order
|
||||
read -r -a order <<<"$sensors"
|
||||
|
||||
idx() {
|
||||
local n
|
||||
for n in "${!order[@]}"; do
|
||||
if [ "${order[$n]}" = "$1" ]; then
|
||||
echo "$n"
|
||||
return
|
||||
fi
|
||||
done
|
||||
echo 999
|
||||
}
|
||||
|
||||
for sensor in sobelow coverage crap; do
|
||||
[ "$(idx "$sensor")" -ne 999 ] || fail "VERIFY_SENSORS does not list '$sensor' (line: $line)"
|
||||
done
|
||||
[ "$(idx sobelow)" -lt "$(idx coverage)" ] ||
|
||||
fail "VERIFY_SENSORS puts coverage before sobelow (cheapest first)"
|
||||
[ "$(idx sobelow)" -lt "$(idx crap)" ] ||
|
||||
fail "VERIFY_SENSORS puts crap before sobelow (cheapest first)"
|
||||
}
|
||||
|
||||
# --- T2: the sobelow target fails loudly and has no skip/fallback path -----
|
||||
# The stickies regression: the user explicitly removed a "not installed,
|
||||
# skipping" fallback because a silently-skipped check is a lying sensor.
|
||||
check_sobelow_recipe() {
|
||||
local recipe
|
||||
recipe=$(awk '
|
||||
/^sobelow:/ { found = 1; next }
|
||||
found && /^\t/ { print; next }
|
||||
found { exit }
|
||||
' "$app_makefile")
|
||||
|
||||
if [ -z "$recipe" ]; then
|
||||
fail "app/Makefile has no sobelow: target with a recipe"
|
||||
return
|
||||
fi
|
||||
|
||||
case "$recipe" in
|
||||
*"mix sobelow"*) ;;
|
||||
*) fail "sobelow recipe does not invoke 'mix sobelow': $recipe" ;;
|
||||
esac
|
||||
|
||||
case "$recipe" in
|
||||
*"$SOBELOW_FAIL_FLAG"*) ;;
|
||||
*) fail "sobelow recipe is missing $SOBELOW_FAIL_FLAG, so findings would exit 0: $recipe" ;;
|
||||
esac
|
||||
|
||||
local banned
|
||||
for banned in '||' 'command -v sobelow' 'skipping' '2>/dev/null'; do
|
||||
case "$recipe" in
|
||||
*"$banned"*) fail "sobelow recipe contains a fallback/skip path ('$banned'): $recipe" ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
# --- T3: every per-finding skip carries a written rationale ----------------
|
||||
# Sobelow 0.17 keeps per-finding skips as fingerprinted lines in
|
||||
# `app/.sobelow-skips` (enabled with `--skip`); `.sobelow-conf` holds only
|
||||
# options. A skip entry without a reason is a defect: the next reader cannot
|
||||
# tell a false positive from a debt someone hid. Comments are preserved by
|
||||
# sobelow's skip loader (they sort first and never match a fingerprint).
|
||||
check_skips() {
|
||||
[ -f "$skips" ] || return 0
|
||||
|
||||
local prev="" line n=0
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
n=$((n + 1))
|
||||
case "$line" in
|
||||
"" | "#"*)
|
||||
prev="$line"
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
case "$prev" in
|
||||
"#"*) ;;
|
||||
*) fail "app/.sobelow-skips:$n: skip entry has no rationale comment on the line above: $line" ;;
|
||||
esac
|
||||
prev="$line"
|
||||
done <"$skips"
|
||||
}
|
||||
|
||||
check_verify_sensors
|
||||
check_sobelow_recipe
|
||||
check_skips
|
||||
|
||||
if [ $status -eq 0 ]; then
|
||||
echo "lens sensors ok: sobelow wired first in VERIFY_SENSORS, fail-loud recipe, app skips rationalised"
|
||||
else
|
||||
echo >&2 "fix the Lens sensor wiring/skips above, then re-run."
|
||||
fi
|
||||
exit $status
|
||||
Reference in New Issue
Block a user