From 05f9520d880a8c8d40f4d80b4d4ca7acc3575a1f Mon Sep 17 00:00:00 2001 From: Atriya Patel Date: Tue, 29 Sep 2026 15:12:09 +0530 Subject: [PATCH 1/2] =?UTF-8?q?chore(ci):=20temporary=20diagnostic=20?= =?UTF-8?q?=E2=80=94=20toolchain=20banner=20and=20failure=20detail?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Not for merge. Adds a one-time toolchain banner to the suite and makes two store-check assertions report the tool's own output, so the macOS runner's CI log says WHY 16 checks fail there instead of only which. --- scripts/test-executor.sh | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/scripts/test-executor.sh b/scripts/test-executor.sh index da13212..e911cf4 100755 --- a/scripts/test-executor.sh +++ b/scripts/test-executor.sh @@ -14,6 +14,16 @@ S="$ROOT/skills/executor/scripts" WORK="$(mktemp -d "${TMPDIR:-/tmp}/executor-tests.XXXXXX")" cleanup() { rm -rf "$WORK"; } trap cleanup EXIT + +# A suite that fails on someone else's machine and passes on yours is +# usually a toolchain difference, not a logic difference. Print what the +# suite is actually running against, once, so a CI log carries the answer +# instead of the next person having to ask. +echo "# toolchain: bash $BASH_VERSION | $(uname -s) $(uname -m) | ${LANG:-unset}" +echo "# awk: $(awk --version 2>&1 | head -1 || awk -W version 2>&1 | head -1)" +echo "# grep: $(grep --version 2>/dev/null | head -1 || echo 'BSD grep (no --version)')" +echo "# sed: $(sed --version 2>/dev/null | head -1 || echo 'BSD sed (no --version)')" +echo "# date: $(date -u +%Y-%m-%dT%H:%M:%SZ)" pass=0; fail=0 ok() { pass=$((pass + 1)); echo "ok - $1"; } bad() { fail=$((fail + 1)); echo "FAIL - $1" >&2; } @@ -344,7 +354,7 @@ d=$(fixture stored7) cd "$d" bash "$S/exec-initiative" new Store > /dev/null 2>&1 SDIR="$d/docs/executor/INIT-0001-store" -bash "$S/exec-store-check" >/dev/null 2>&1 || bad "stored7: fresh seeded initiative failed store check" +sc=$(bash "$S/exec-store-check" 2>&1) || bad "stored7: fresh seeded initiative failed store check: $sc" printf -- '\n\n' >> "$SDIR/charter.md" if bash "$S/exec-store-check" >/dev/null 2>&1; then bad "stored7: HTML comment in charter passed store check" @@ -481,7 +491,7 @@ if bash "$S/exec-store-check" >/dev/null 2>&1; then bad "archdia: mermaid nested inside a markdown fence counted" fi { printf '%s\n' "$ARCH"; printf '```mermaid\nflowchart TD\n A --> B\n```\n'; } > "$SDIR/architecture/INIT-0001-ARCH-01-main.md" -bash "$S/exec-store-check" > /dev/null 2>&1 || bad "archdia: arch doc with mermaid rejected" +sc=$(bash "$S/exec-store-check" 2>&1) || bad "archdia: arch doc with mermaid rejected: $sc" ok "D8 requires a real top-level mermaid diagram in architecture" # 26. Store check D8 spec contract: numbered ### R headings, a From 79bd7e260391b663fdb004a2f1bfc6222eca2c6e Mon Sep 17 00:00:00 2001 From: Atriya Patel Date: Tue, 29 Sep 2026 15:24:43 +0530 Subject: [PATCH 2/2] fix: exec-store-check died on bash 3.2, silently skipping every check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The macOS CI job failed 16 checks that pass on my machine AND on Ubuntu. The log named which tests failed but not why, so this carries a one-time toolchain banner and makes the store-check assertions report the tool's own output — a failing test should say why, and a suite that only passes on the author's machine should say what it is running against. Cause: 'declare -A LISTED=()' in exec-store-check. Associative arrays are bash 4.0+, and the scripts use '#!/usr/bin/env bash' — which on a stock macOS resolves to /bin/bash 3.2, the same 3.2.57 the GitHub macOS runner has. Under 3.2 that declaration is a hard error, and because the script runs under 'set -e' it died on that line: every I1/I2/I3 check after it never ran, so a perfectly clean store was reported as rejected, sixteen times over. Nothing in the output said so. It was the only bash 4.0+ construct in the codebase; a scan for mapfile, readarray, coproc, ;;& and others found nothing else. Replaced the associative array with a newline-delimited set — same job, works in 3.2 — with a whole-line membership test so INIT-0001-P01 never matches INIT-0001-P011, which a bare substring test would. Verified: 123/123 on /bin/bash 3.2.57 AND on bash 5.3. A new assertion guards the floor by scanning the shipped scripts for bash 4.0+ constructs. The suite's own compatibility is enforced by the macos-latest matrix job, which runs it on the stock 3.2 interpreter. --- scripts/test-executor.sh | 29 ++++++++++++++++++++++++ skills/executor/scripts/exec-store-check | 16 ++++++++++--- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/scripts/test-executor.sh b/scripts/test-executor.sh index e911cf4..8f2d167 100755 --- a/scripts/test-executor.sh +++ b/scripts/test-executor.sh @@ -27,6 +27,35 @@ echo "# date: $(date -u +%Y-%m-%dT%H:%M:%SZ)" pass=0; fail=0 ok() { pass=$((pass + 1)); echo "ok - $1"; } bad() { fail=$((fail + 1)); echo "FAIL - $1" >&2; } + +# The scripts use `#!/usr/bin/env bash`, which resolves to whatever bash is +# first on PATH — on a stock macOS that is /bin/bash 3.2, and on a GitHub +# macOS runner it is exactly the same. So the floor is bash 3.2, not +# "whatever the author happens to have installed". +# +# `declare -A` (bash 4.0) failed there, and because exec-store-check runs +# under `set -e` the failure killed it at that line: every check after it +# never ran, and sixteen tests reported a perfectly clean store as +# "rejected". A construct that is harmless on the author's machine and fatal +# on the user's is the worst kind there is, and nothing about the failure +# said so — the tests simply went red. Assert the floor mechanically. +# Scoped to the scripts the engine EXECUTES. `declare -a` (indexed) is +# fine in 3.2 — only `declare -A` (associative) needs 4.0 — and matching `-A` +# rather than `-[aA]` is the difference between a check that works and one +# that reports a false positive until someone deletes it. The suite's own +# 3.2 compatibility is not this check's job: the macos-latest matrix job +# runs it on the stock 3.2 interpreter, which is a stronger check than any +# grep. +bash4_constructs() { + /usr/bin/grep -rnE 'declare[[:space:]]+-A[[:space:]]|mapfile|readarray|[[:space:]]coproc[[:space:]]' \ + "$ROOT/skills/executor/scripts" 2>/dev/null \ + | /usr/bin/grep -vE ':[[:space:]]*#' | cut -d: -f1 | sort -u +} +if [ -n "$(bash4_constructs)" ]; then + bad "bash 3.2: bash 4.0+ construct in $(bash4_constructs | tr '\n' ' ')" +else + ok "bash 3.2: no bash 4.0+ construct in the shipped scripts" +fi fixture() { local d="$WORK/$1"; mkdir -p "$d" git -C "$d" init -q -b main diff --git a/skills/executor/scripts/exec-store-check b/skills/executor/scripts/exec-store-check index db00632..4b26b36 100755 --- a/skills/executor/scripts/exec-store-check +++ b/skills/executor/scripts/exec-store-check @@ -254,14 +254,21 @@ for idir in "$store"/INIT-*/; do # ---- I1/I2/I3: documents table vs disk ----------------------------------- if [ -f "$idx" ]; then - declare -A LISTED=() + # Newline-delimited set, not `declare -A`. Associative arrays are + # bash 4.0+, and macOS still ships bash 3.2 as /bin/bash — which is what + # a GitHub macOS runner puts on PATH ahead of any newer bash. Under 3.2 + # `declare -A` is a hard error, and with `set -e` the script dies at this + # line, so every check downstream of it silently never ran. The symptom + # was sixteen green-looking checks reporting "rejected" on a store that + # was fine. A newline-delimited string does the same job in 3.2. + LISTED=$'\n' while IFS= read -r row; do row=${row#\|}; row=${row%\|} # trim outer pipes IFS='|' read -r did _ _ status path <<< "$row" did=$(printf '%s' "$did" | xargs) [ -n "$did" ] || continue case "$did" in INIT-[0-9][0-9][0-9][0-9]-*) ;; *) continue ;; esac - LISTED["$did"]=1 + LISTED+="$did"$'\n' path=$(printf '%s' "$path" | xargs) path=${path#\`}; path=${path%\`} if [ -n "$path" ] && [ ! -f "$idir/$path" ]; then @@ -282,7 +289,10 @@ for idir in "$store"/INIT-*/; do base=$(basename "$f") seg=$(printf '%s' "$base" | grep -oE '^INIT-[0-9]{4}-(CHTR|RSCH|OPTS|ARCH|ADR|IFCE|DSGN|SPEC|RISK|VRFY)-[0-9]+|^INIT-[0-9]{4}-P[0-9]{2}') || seg="" [ -n "$seg" ] || { note "unregistered-content file skipped: $rel (no document ID segment)"; continue; } - [ -n "${LISTED[$seg]:-}" ] || fail "I1: $rel exists on disk but has no Documents-table row in $init_id INDEX.md" + # Membership: the set carries a leading and trailing newline, so a + # segment can only match a WHOLE line — INIT-0001-P01 never matches + # INIT-0001-P011, which a bare substring test would. + [ "${LISTED#*$'\n'"$seg"$'\n'}" != "$LISTED" ] || fail "I1: $rel exists on disk but has no Documents-table row in $init_id INDEX.md" done < <(find "$idir" -type f -name '*.md' \ ! -name 'INDEX.md' ! -name 'README.md' ! -path '*/brainstorm/*' ! -path '*/evidence/*' | sort)