ci/woodpecker/pr/ci Pipeline failed
rev-974 (#1085 review 130) found three blockers. This closes the first two. [1] SANDBOX ESCAPE. The test ran under `set -uo pipefail` with unchecked mkdir, calls-log redirect and `cd "$REPO_DIR"`, then prepended a possibly-nonexistent $MOCK_BIN to PATH -- while `git remote add origin` names the REAL repository. rev-974 forced setup failure with an unwritable AGENT_WORK_ROOT and the test continued past every error, ran `git init` in its CALLER's directory, added the real origin, and invoked its target: TARGET_REACHED args=-i 42 -c closing note With the committed target that is the real, provider-mutating issue-close.sh. ShellCheck flagged the unguarded cd as SC2164 independently. Now: `set -euo pipefail`, every setup step checked with a legible reason, and assert_mocked() proves BOTH `tea` and `curl` resolve inside $MOCK_BIN before any target invocation. Control: unwritable AGENT_WORK_ROOT -> rc=1 at mkdir, target never reached. [2] THE API/no-login BRANCH HAD NO DISCRIMINATING COVERAGE. The mock always returned a tea login, so `gitea_issue_comment_api || fail-closed` was never executed. rev-974 replaced the whole fallback contract with an unconditional close -- silently dropping the comment -- and the committed test still passed rc=0. Added three cases asserting the POSTCONDITION (which HTTP calls happened, in what order) rather than that a command ran: comment POST fails -> no PATCH and non-zero; comment succeeds -> strictly POST,PATCH; no comment requested -> PATCH only, never a POST. The curl mock now records method and URL. Control: replaying rev-974's contract destruction now fails with "API path: no comment POST attempted". Two self-inflicted traps hit while adding `set -e`, both the same family as the #1086 defect be-coder-08 found, and both silent: - `grep -q X "$CALLS" && fail "..."` -- the ABSENT case (grep rc=1, the PASSING case for a must-not-appear assertion) is the last command of an && list and terminates the script with no message. All four converted to if-blocks. - `run_target ...; rc=$?` -- the function's non-zero RETURN trips set -e in the CALLER before rc is read; run_target's internal `set +e` protects the target, not the caller. All five call sites now `rc=0; run_target ... || rc=$?`. Controls: unchanged main -> rc=1 "used 'tea issue comment'" fallback contract destroyed -> rc=1 "API path: no comment POST attempted" unwritable AGENT_WORK_ROOT -> rc=1 at setup, target never invoked fixed source -> rc=0 [3] remains open: with MOSAIC_GIT_IDENTITY=rev-974 the wrapper resolves GITEA_LOGIN_NAME=mosaicstack-mos, so one principal holds but the operation is attributed to Mos rather than the requested seat. That changes identity resolution shared by every wrapper in this directory and is not folded in here. Reported-by: rev-974