ci/woodpecker/pr/ci Pipeline failed
Upstream of brain commits 08a00149 + 971586ef + the arm-6 sandbox fix (brain 5th commit of 2026-08-28 series): - R1: -b/--body is the canonical comment flag (matches issue-create, issue-edit, pr-create, pr-edit); -c/--comment stays a compatible alias. - R4: usage errors print to stderr and exit 2, distinct from provider, credential, and verification failures (exit 1). Value-less flags fail loudly (previously -c with no value died silently at rc=1 via set -e on the failed shift 2). gh failures on the GitHub path normalize to exit 1 (gh's own usage errors exit 2 and would collide with the reserved code). - Tests: new usage-contract suite (help rc, unknown/missing/value-less rc=2 on stderr, alias parse acceptance under a sandboxed runner, GitHub-path exit normalization with a stubbed gh, zero provider contact on parser failure); readback suite gains case 11 (full verified write via -b) and neutralizes seat-exported MOSAIC_GIT_IDENTITY / MOSAIC_BRAIN_HOME that escape the sandboxed HOME (documented HTTP 401 / fail-loud shapes). Driver: a fleet seat full-stopped on an issue-comment usage error because usage failures were indistinguishable from provider failures and the stop gate treated every wrapper failure as blocking.
166 lines
6.7 KiB
Bash
Executable File
166 lines
6.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Usage-error contract for issue-comment.sh (R1/R4 remediation, 2026-08-28).
|
|
#
|
|
# R4: usage errors print to STDERR and exit 2, distinct from provider,
|
|
# credential, and verification failures (exit 1), so a caller (or a stop gate)
|
|
# can tell an invocation defect from a delivery blocker. Before this contract
|
|
# the wrapper exited 1 for usage errors with messages on STDOUT, and a
|
|
# value-less flag (-c with no value) died SILENTLY at rc=1 because set -e
|
|
# killed the failed `shift 2`. That silent shape is what full-stopped a fleet
|
|
# seat: a caller could not distinguish "I invoked it wrong" from "delivery is
|
|
# blocked".
|
|
#
|
|
# R1: -b/--body is the canonical comment flag (matching issue-create,
|
|
# issue-edit, pr-create, pr-edit); -c/--comment remains a backward-compatible
|
|
# alias.
|
|
#
|
|
# Arms:
|
|
# 1. --help and -h exit 0 and print usage.
|
|
# 2. Unknown option exits 2 with the message on stderr.
|
|
# 3. Missing required -i exits 2 (stderr).
|
|
# 4. Missing required comment exits 2 (stderr).
|
|
# 5. A value-less flag (-i -b -c -l and long forms) exits 2 with a
|
|
# "requires a value" message on stderr (the former silent-death class).
|
|
# 6. -b and -c both pass parsing (the run then fails at platform detection
|
|
# in this non-repo fixture, nonzero and NOT 2), proving alias acceptance
|
|
# without any provider fixture.
|
|
# 7. No arm performs any provider request: PATH shims for gh/tea/curl
|
|
# record every invocation and the probe log must stay empty.
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
WORK_DIR="${MOSAIC_TEST_WORK_DIR:-$PWD/.mosaic-test-work/issue-comment-usage}"
|
|
BIN_DIR="$WORK_DIR/bin"
|
|
PROBE_LOG="$WORK_DIR/provider-probes.log"
|
|
OUT_FILE="$WORK_DIR/out.log"
|
|
ERR_FILE="$WORK_DIR/err.log"
|
|
|
|
cleanup() {
|
|
rm -rf "$WORK_DIR"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
mkdir -p "$BIN_DIR"
|
|
: > "$PROBE_LOG"
|
|
|
|
# Provider shims: any invocation is recorded and fails the run at the end.
|
|
# Usage-error arms must exit during argument parsing, before detect_platform,
|
|
# so these prove "no provider request on parser failure".
|
|
for tool in gh tea curl; do
|
|
cat > "$BIN_DIR/$tool" <<STUB
|
|
#!/usr/bin/env bash
|
|
echo "$tool \$*" >> "$PROBE_LOG"
|
|
# gh doubles as platform probe AND write path in arm 6b: probes exit 0; the
|
|
# comment write exits 2 (gh's own usage-error status) to prove the wrapper
|
|
# normalizes provider failures to exit 1 instead of propagating 2.
|
|
if [[ "\$1 \$2" == "issue comment" ]]; then exit 2; fi
|
|
exit 0
|
|
STUB
|
|
chmod +x "$BIN_DIR/$tool"
|
|
done
|
|
|
|
run_wrapper() {
|
|
( cd "$WORK_DIR" && PATH="$BIN_DIR:$PATH" "$SCRIPT_DIR/issue-comment.sh" "$@" )
|
|
}
|
|
|
|
# Hermetic variant for parse-acceptance arms: neutralizes every identity/
|
|
# credential source the wrapper consults (seat env vars, HOME, XDG tea config)
|
|
# so the arm fails at credential resolution in ANY cwd repo, never reading a
|
|
# real token or contacting a provider. Measured 2026-08-28: without this, the
|
|
# arm's outcome depended on incidental URL-resolution state (brain cwd died at
|
|
# URL-not-found; a stack worktree cwd resolved a configured URL, read the real
|
|
# seat token, and invoked the curl stub — the suite then failed its own
|
|
# no-provider-contact check, correctly).
|
|
run_wrapper_sandboxed() {
|
|
mkdir -p "$WORK_DIR/home" "$WORK_DIR/xdg"
|
|
(
|
|
cd "$WORK_DIR"
|
|
PATH="$BIN_DIR:$PATH" HOME="$WORK_DIR/home" XDG_CONFIG_HOME="$WORK_DIR/xdg" \
|
|
MOSAIC_GIT_IDENTITY="" MOSAIC_BRAIN_HOME="" \
|
|
"$SCRIPT_DIR/issue-comment.sh" "$@"
|
|
)
|
|
}
|
|
|
|
fail() {
|
|
echo "FAIL: $*" >&2
|
|
echo "--- stderr ---" >&2
|
|
cat "$ERR_FILE" >&2
|
|
exit 1
|
|
}
|
|
|
|
expect_rc() { # expect_rc <want> <desc> <args...>
|
|
local want="$1" desc="$2" rc=0
|
|
shift 2
|
|
run_wrapper "$@" >"$OUT_FILE" 2>"$ERR_FILE" || rc=$?
|
|
[[ "$rc" -eq "$want" ]] || fail "$desc: rc=$rc, want $want"
|
|
}
|
|
|
|
expect_stderr() { # expect_stderr <pattern> <desc>
|
|
grep -q "$1" "$ERR_FILE" || fail "$desc: stderr missing '$1'"
|
|
}
|
|
|
|
# 1. Help exits 0 and prints usage on stdout.
|
|
expect_rc 0 "--help exits 0" --help
|
|
grep -q "Usage: issue-comment.sh" "$OUT_FILE" || fail "--help did not print usage"
|
|
expect_rc 0 "-h exits 0" -h
|
|
|
|
# 2. Unknown option: rc 2, message on stderr.
|
|
expect_rc 2 "unknown option exits 2" --bogus
|
|
expect_stderr "unknown option" "unknown option names itself on stderr"
|
|
|
|
# 3. Missing required issue number: rc 2, stderr.
|
|
expect_rc 2 "missing -i exits 2"
|
|
expect_stderr "issue number is required" "missing -i message on stderr"
|
|
|
|
# 4. Missing required comment: rc 2, stderr.
|
|
expect_rc 2 "missing comment exits 2" -i 5
|
|
expect_stderr "comment is required" "missing comment message on stderr"
|
|
|
|
# 5. Value-less flags: rc 2 with "requires a value" on stderr. The old parser
|
|
# died here silently (set -e on the failed shift 2).
|
|
for flag in -i -b -c -l --issue --body --comment --login; do
|
|
expect_rc 2 "value-less $flag exits 2" "$flag"
|
|
expect_stderr "requires a value" "value-less $flag message on stderr"
|
|
done
|
|
|
|
# 6. Alias acceptance at parse level: both -b and -c carry a value past
|
|
# parsing; the wrapper then fails at platform detection (not a git repo)
|
|
# nonzero but NOT as a usage error (rc must not be 2).
|
|
for flag in -b -c; do
|
|
rc=0
|
|
run_wrapper_sandboxed -i 5 "$flag" "some text" >"$OUT_FILE" 2>"$ERR_FILE" || rc=$?
|
|
[[ "$rc" -ne 0 ]] || fail "$flag arm unexpectedly succeeded in the sandbox"
|
|
[[ "$rc" -ne 2 ]] || fail "$flag arm misclassified credential failure as a usage error"
|
|
done
|
|
|
|
# 6b. GitHub-path exit normalization (codex blocker on 08a00149): gh's own
|
|
# usage errors exit 2; the wrapper must NOT propagate that status (reserved
|
|
# for the wrapper's usage-error contract). With a github remote and a gh stub
|
|
# whose comment write exits 2, the wrapper must exit 1 with the normalized
|
|
# error on stderr.
|
|
GH_REPO="$WORK_DIR/repo-gh"
|
|
mkdir -p "$GH_REPO"
|
|
git -C "$GH_REPO" init -q
|
|
git -C "$GH_REPO" remote add origin https://github.com/acme/widgets.git
|
|
git -C "$GH_REPO" config mosaic.gitIdentity ""
|
|
rc=0
|
|
(
|
|
cd "$GH_REPO"
|
|
PATH="$BIN_DIR:$PATH" MOSAIC_GIT_IDENTITY="" MOSAIC_BRAIN_HOME="" \
|
|
"$SCRIPT_DIR/issue-comment.sh" -i 5 -b "text" >"$OUT_FILE" 2>"$ERR_FILE"
|
|
) || rc=$?
|
|
[[ "$rc" -eq 1 ]] || fail "GitHub path: gh exit 2 must normalize to wrapper exit 1 (got $rc)"
|
|
grep -q "GitHub comment write failed" "$ERR_FILE" || fail "GitHub path: normalized error missing from stderr"
|
|
grep -q "^gh issue comment" "$PROBE_LOG" || fail "GitHub path: gh write was not invoked"
|
|
|
|
# 7. No provider contact from any usage-error arm (arm 6b's deliberate gh
|
|
# invocation is the only permitted entry in the probe log).
|
|
if grep -v '^gh issue comment' "$PROBE_LOG" | grep -q .; then
|
|
echo "FAIL: a parser-failure arm contacted a provider:" >&2
|
|
grep -v '^gh issue comment' "$PROBE_LOG" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "issue-comment.sh usage-contract regression passed (R1/R4)"
|