fix(portability): stop assuming GNU coreutils flags on macOS

BSD userland rejects or silently ignores several GNU spellings the repo used:

- `timeout` is not in a stock macOS at all (Homebrew installs it, and also as
  `gtimeout`). Without it every gates.sh check exited 127 and was recorded
  NOT-MET whatever the check actually did — a systematic false negative.
  The binary is now resolved once, with a pure-bash deadline behind it so the
  124 contract still holds where neither binary exists. GATES_TIMEOUT_BIN is
  overridable with `-` not `:-`, so an empty value forces that fallback: the
  suite passes 64/64 both ways, timeout cases included.
- `touch -d '10 days ago'` is GNU-only; perl's utime is the one spelling both
  platforms ship.
- BSD `wc -l` pads its count with leading spaces, so string compares failed as
  got[      48] want[48].
- `sed -i` needs a suffix argument on BSD AND does not expand \n in the
  replacement, so the release-candidate CHANGELOG edit silently did nothing
  and the assertion failed for the wrong reason. Rewritten in awk; the RC_TAG=0
  mode still REDs on the absent tag, so the test keeps its teeth.
- `/bin/grep` does not exist on macOS (grep lives in /usr/bin), and `stat -c`
  is GNU-only.

fast-libs 11/11, seo-data 221/221, release-candidate 5 GREEN / 0 RED.
This commit is contained in:
2026-09-13 17:21:09 -04:00
parent a53a5a26a8
commit a4565c80c3
4 changed files with 52 additions and 10 deletions
+33 -1
View File
@@ -30,6 +30,13 @@
set -uo pipefail
TIMEOUT="${GATES_TIMEOUT:-120}"
# GNU coreutils' `timeout` ships on Linux but NOT on a stock macOS (Homebrew
# installs it as both `timeout` and `gtimeout`). Resolve it once: without it
# every check exits 127 and reports NOT-MET whatever the check actually did.
# Overridable, and with `-` not `:-` so an explicitly EMPTY value forces the
# pure-bash path — that is how the fallback gets exercised on a machine that
# does have the binary.
GATES_TIMEOUT_BIN="${GATES_TIMEOUT_BIN-$(command -v timeout || command -v gtimeout || true)}"
EVIDENCE_CAP=140
# Module-level parse tables, index-aligned. Bash has no record type; threading
@@ -165,9 +172,34 @@ _decisive() { # _decisive <combined-output>
# Fail-closed: exit 0 AND the marker. A nonzero process never passes because
# its error text happens to contain the expected token.
# Same contract as `timeout`: run the command, return 124 if it outruns <secs>.
# Pure-bash stand-in for a platform shipping neither binary, so the deadline
# stays real instead of silently degrading into "every gate NOT-MET".
_gates_timeout() { # _gates_timeout <secs> <cmd>...
local secs="$1"; shift
[ -n "$GATES_TIMEOUT_BIN" ] && { "$GATES_TIMEOUT_BIN" "$secs" "$@"; return $?; }
local waited=0 pid
"$@" &
pid=$!
while kill -0 "$pid" 2>/dev/null; do
if [ "$waited" -ge "$secs" ]; then
# Park the shell's stderr: bash announces a signal-killed job on ITS
# stderr, which the caller captures with 2>&1 and would read as output.
exec 3>&2 2>/dev/null
kill -TERM "$pid" 2>/dev/null || true
wait "$pid" 2>/dev/null || true
exec 2>&3 3>&-
return 124
fi
sleep 1
waited=$((waited + 1))
done
wait "$pid"
}
_run_one() { # _run_one <idx>
local i="$1" out rc
out="$(timeout "$TIMEOUT" bash -c "${_CHECK[i]}" 2>&1)"
out="$(_gates_timeout "$TIMEOUT" bash -c "${_CHECK[i]}" 2>&1)"
rc=$?
_STATUS[i]="NOT-MET"
if [ "$rc" -eq 124 ]; then