From a81347fbc5f174e7b83c1c0d25398d8bcd695286 Mon Sep 17 00:00:00 2001 From: InauguralPhysicist Date: Sun, 6 Sep 2026 16:16:58 -0500 Subject: [PATCH] fix: failsoft_classify_check portable to macOS tools; stamp v0.43.0 The v0.43.0 release build failed on both macOS jobs in tools/failsoft_classify_check.sh, which shipped two tool-version assumptions macOS violates: - `declare -A tally` + `tally[$tag]` (bash 4+): macOS bash 3.2 makes `declare -A` a syntax error and parses the string key as arithmetic on an unbound name, which `set -u` kills. Replaced with plain per-tag counters + case dispatch. - `make --eval` in compiled_union (GNU Make 3.82+): macOS ships Make 3.81, where the flag is unknown and the output empty, so every builtin file read as an ORPHAN. Replaced with a temp makefile that `include`s the real one and carries the print rule (3.81-compatible). This was masked by the declare -A crash until it was fixed; the #988 [99p] child-exit ledger surfaces both. Semantics unchanged (files=13 sites=136; ANSWER=70 CHANNEL=36 LITERAL=3 EMPTY=6 STRICT=7 TODO=14; selftest 13/13). No other child .sh uses a bash-4/make-3.82 construct. Also stamps CLAUDE.md Latest-release to v0.43.0 now that the tag exists (doc_drift_check gates it against the latest tag). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Kpzyjv1SaLaqBf45FSFDhB --- CLAUDE.md | 2 +- tools/failsoft_classify_check.sh | 40 +++++++++++++++++++++++++++----- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index f7f2e978..3cdb938a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -135,7 +135,7 @@ Always-on: ## Current state & where the detail lives -- **Latest release: v0.42.0** (2026-08-27). Unreleased work on `main`: see +- **Latest release: v0.43.0** (2026-09-06). Unreleased work on `main`: see CHANGELOG.md `[Unreleased]`. Full version history: **CHANGELOG.md** (don't re-narrate it here — tools/doc_drift_check.sh FAILS the suite when this line falls behind the latest tag). Roadmap: **ROADMAP.md**. diff --git a/tools/failsoft_classify_check.sh b/tools/failsoft_classify_check.sh index 8fbbad64..ab4ad608 100755 --- a/tools/failsoft_classify_check.sh +++ b/tools/failsoft_classify_check.sh @@ -139,10 +139,19 @@ usage_mode="${1:-}" # Covered set = (b); (b) must be a subset of (a). A builtin-defining file # outside every variant is reported as an ORPHAN, not silently skipped. compiled_union() { - local root="$1" v + # Expand each SRC_V_ Makefile variable to its source list. `make + # --eval` (used originally) needs GNU Make 3.82+; macOS ships 3.81, where + # the flag is unknown and the output is empty, so every builtin file reads + # as an ORPHAN. A temp makefile that includes the real one and carries the + # print rule is 3.81-compatible. + local root="$1" v tmpmk + tmpmk="$(mktemp)" + { printf 'include %s/Makefile\n' "$root" + printf '__fsp-%%:\n\t@echo $($*)\n'; } > "$tmpmk" for v in $(grep -oE '^SRC_V_[a-z-]+' "$root/Makefile" | sed 's/^SRC_V_//'); do - make -C "$root" --eval='__fsp-%: ; @echo $($*)' -s "__fsp-SRC_V_$v" 2>/dev/null + make -C "$root" -f "$tmpmk" -s "__fsp-SRC_V_$v" 2>/dev/null done | tr ' ' '\n' | sed '/^$/d' | sort -u + rm -f "$tmpmk" } builtin_files() { @@ -250,7 +259,11 @@ run_check() { # -> 0 clean / 1 finding ; prints a summary covered="$(builtin_files "$root")" local n_files=0 n_hits=0 n_bad=0 n_tagged=0 orphan=0 splits=0 - declare -A tally=() + # Per-tag counters as plain variables, not an associative array: macOS + # ships bash 3.2, where `declare -A` is a syntax error and `tally[$tag]` + # with a string key is parsed as arithmetic on an unbound name (release + # build failed here — the [99p] child-exit ledger surfaced it). + local t_ANSWER=0 t_CHANNEL=0 t_LITERAL=0 t_EMPTY=0 t_STRICT=0 t_TODO=0 local c for c in $covered; do if ! grep -qxF "$c" <<<"$compiled"; then @@ -293,7 +306,14 @@ run_check() { # -> 0 clean / 1 finding ; prints a summary continue fi n_tagged=$((n_tagged + 1)) - tally[$tag]=$(( ${tally[$tag]:-0} + 1 )) + case "$tag" in + ANSWER) t_ANSWER=$((t_ANSWER + 1)) ;; + CHANNEL) t_CHANNEL=$((t_CHANNEL + 1)) ;; + LITERAL) t_LITERAL=$((t_LITERAL + 1)) ;; + EMPTY) t_EMPTY=$((t_EMPTY + 1)) ;; + STRICT) t_STRICT=$((t_STRICT + 1)) ;; + TODO) t_TODO=$((t_TODO + 1)) ;; + esac [ "$usage_mode" = "--verbose" ] && printf ' %-8s %s:%s\n' "$tag" "$f" "$ln" done < <(norm_hits "$root/$f") done @@ -315,9 +335,17 @@ run_check() { # -> 0 clean / 1 finding ; prints a summary printf ' files=%d sites=%d classified=%d unclassified=%d floor=%d\n' \ "$n_files" "$n_hits" "$n_tagged" "$n_bad" "$FLOOR_SITES" - local t out="" + local t c out="" for t in ANSWER CHANNEL LITERAL EMPTY STRICT TODO; do - out="$out $t=${tally[$t]:-0}" + case "$t" in + ANSWER) c=$t_ANSWER ;; + CHANNEL) c=$t_CHANNEL ;; + LITERAL) c=$t_LITERAL ;; + EMPTY) c=$t_EMPTY ;; + STRICT) c=$t_STRICT ;; + TODO) c=$t_TODO ;; + esac + out="$out $t=$c" done echo " $out"