From dd5844f48854ed5c7a28c6545b20d979e2dcfe03 Mon Sep 17 00:00:00 2001 From: Tiago Peixoto Date: Sat, 5 Sep 2026 04:17:06 -0300 Subject: [PATCH 01/11] fix(tests): isolate fixture Git configuration from host preferences Ignore global and system Git configuration in the shared test library, which all four fixture helper entry points source. Keep local config, command-line overrides and explicitly supplied test config usable without changing the caller's environment or real project signing preferences. Exercise global and system signing inputs through all four helpers, real fixture and child commits, explicit signing overrides, unchanged input files, and signing refusal outside fixture subprocesses. Verification evidence for issue #3770: On pristine upstream f09de8a3, all 12 reported suites failed and each logged "No secret key" using a private GIT_CONFIG_GLOBAL containing commit.gpgsign=true and gpg.format=openpgp, GIT_CONFIG_NOSYSTEM=1, and an empty private GNUPGHOME (GIT_CONFIG_COUNT and GIT_CONFIG_PARAMETERS unset). With this change, all 12 pass in the identical environment through bin/fm-test-run.sh --per-script-timeout-secs 900: fm-backlog-atomicity, fm-bootstrap-network-parallel, fm-bootstrap, fm-crew-state, fm-fleet-sync, fm-gate-refuse, fm-grok-harness, fm-session-start, fm-sessionstart-nudge, fm-tangle-guard, fm-test-run, and fm-update (all tests/.test.sh). The new fm-test-fixtures regression failed before the library change and passes after it. Canonical bin/fm-lint.sh passes. Additional verification exposed fm-teardown's herdr-preflight-missing-adapter assertion on both this branch and an unchanged f09de8a3 archive with signing neutralized. That pre-existing failure needs separate disposition; it is not repaired or skipped here. The separately owned Muse and composer fixture defects remain untouched. Fixes #3770 --- tests/fm-test-fixtures.test.sh | 52 +++++++++++++++++++++++++++++++++- tests/lib.sh | 6 ++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/tests/fm-test-fixtures.test.sh b/tests/fm-test-fixtures.test.sh index 1ee5baa3a77..937881a11a3 100755 --- a/tests/fm-test-fixtures.test.sh +++ b/tests/fm-test-fixtures.test.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Behavior tests for tests/fixtures.sh fake-toolchain and spawn-world builders. +# Behavior tests for shared Git fixtures, fake-toolchain and spawn-world builders. # # These cases drive the builders as a test would: they write stubs into a # fakebin and exec those stubs. Assertions are on the binaries' observable @@ -13,6 +13,55 @@ set -u TMP_ROOT=$(fm_test_tmproot fm-test-fixtures) +test_git_config_isolation() ( + local dir="$TMP_ROOT/git-config" scope helper + mkdir -p "$dir" + export GIT_CONFIG_GLOBAL="$dir/global" GIT_CONFIG_SYSTEM="$dir/system" + export GIT_CONFIG_NOSYSTEM=0 + unset GIT_CONFIG_COUNT GIT_CONFIG_PARAMETERS + # A failing signer exposes inherited config without requiring GPG or keys. + for scope in global system; do + : > "$dir/global" + : > "$dir/system" + git config --file "$dir/$scope" commit.gpgsign true + git config --file "$dir/$scope" gpg.format openpgp + git config --file "$dir/$scope" gpg.program /usr/bin/false + cp "$dir/$scope" "$dir/expected" + for helper in lib fixtures secondmate-helpers wake-helpers; do + bash -eus -- "$ROOT/tests/$helper.sh" "$dir/$scope-$helper" "$dir/$scope" <<'SH' || exit 1 +. "$1" +fm_git_init_commit "$2" +[ "$(git -C "$2" log -1 --format=%s)" = initial ] || fail "fixture has no initial commit" +fm_git_identity +# Child Git processes and direct commits inherit the same isolation. +bash -eu -c 'git -C "$1" commit -q --allow-empty -m child' _ "$2" +# Repository-local config and explicit command inputs remain authoritative. +git -C "$2" config commit.gpgsign true +git -C "$2" config gpg.program /usr/bin/false +if git -C "$2" commit -q --allow-empty -m signed > "$2/signing.log" 2>&1; then + fail "repository-local signing config was ignored" +fi +assert_grep 'gpg failed to sign' "$2/signing.log" "local signing was not attempted" +git -C "$2" -c commit.gpgsign=false commit -q --allow-empty -m explicit +GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=commit.gpgsign GIT_CONFIG_VALUE_0=false \ + git -C "$2" commit -q --allow-empty -m environment +# A config test can deliberately supply its own global file after sourcing. +[ "$(GIT_CONFIG_GLOBAL="$3" git config --get commit.gpgsign)" = true ] || fail "explicit global config was ignored" +SH + done + # Sourcing in test subprocesses cannot change the caller or its config files. + [ "$(git config --get commit.gpgsign)" = true ] || fail "caller lost signing preference" + cmp -s "$dir/$scope" "$dir/expected" || fail "host config file was changed" + git init -q "$dir/$scope-outside" + if git -C "$dir/$scope-outside" -c user.name=test -c user.email=test@example.invalid \ + commit -q --allow-empty -m outside > "$dir/outside.log" 2>&1; then + fail "commit outside fixtures bypassed signing" + fi + assert_grep 'gpg failed to sign' "$dir/outside.log" "outside commit did not attempt signing" + done + pass "shared helpers isolate host Git config and preserve explicit config and outside commits" +) + test_no_mistakes_version_constant() { local fakebin out fakebin=$(fm_fakebin "$TMP_ROOT/nm") @@ -124,6 +173,7 @@ test_spawn_home_layout() { pass "spawn-home layout writes harness pin, beat, and brief" } +test_git_config_isolation || fail "Git fixture config isolation" test_no_mistakes_version_constant test_no_mistakes_init_doctor_markers test_fake_gh_and_gh_axi diff --git a/tests/lib.sh b/tests/lib.sh index e42945de19f..8cb3765a75c 100644 --- a/tests/lib.sh +++ b/tests/lib.sh @@ -33,6 +33,12 @@ FM_TEST_LIB_SOURCED=1 # suite's fixtures were written against. umask 022 +# Fixture Git processes must not inherit host signing, hooks, or other global +# and system preferences. This affects only the sourcing test and its children; +# config tests can still set local config, use -c, or supply their own env after +# sourcing. Never write the developer's config or disable real project signing. +export GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_NOSYSTEM=1 + # Exempt firstmate's own test suite from the gate-lifecycle refusal # (bin/fm-gate-refuse-lib.sh). The no-mistakes gate runs this suite FROM a gate # worktree - the exact environment that guard refuses - so without this every From c1097ad357c645e7a2a03fee3a3bc316b6c9d475 Mon Sep 17 00:00:00 2001 From: Tiago Peixoto Date: Sat, 5 Sep 2026 04:45:17 -0300 Subject: [PATCH 02/11] no-mistakes(review): Complete fixture Git isolation and scope config assertions --- bin/fm-test-run.sh | 1 + tests/fm-test-fixtures.test.sh | 46 ++++++++++++++++++++++++++++++---- tests/herdr-test-safety.sh | 2 ++ 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/bin/fm-test-run.sh b/bin/fm-test-run.sh index bfad5cbb5d2..3dcf547b1cd 100755 --- a/bin/fm-test-run.sh +++ b/bin/fm-test-run.sh @@ -2173,6 +2173,7 @@ record_script_result() { # because an unbounded suite is what silently outruns its caller's budget. run_script_bounded() { #