framework tools/git: P1b — read-only viewers join the R1/R4 contract (pr-diff, pr-view, pr-metadata, pr-list)
usage_error contract (stderr + exit 2), strict option-like value guards, missing -n checks; pr-list usage() default exit 2. Four suites enrolled (population 93). Closes the viewer gap from the #1464 census: carriers now 20 of the 22; remaining non-carriers are the deliberate design-first class (ci-queue-wait, pr-merge, pr-ci-wait, mosaic-worktree).
This commit is contained in:
@@ -13,21 +13,33 @@ OUTPUT_FILE=""
|
||||
REPO_OVERRIDE=""
|
||||
HOST_OVERRIDE=""
|
||||
|
||||
# Usage-error contract (R4): usage errors print to STDERR and exit 2,
|
||||
# distinct from provider, credential, and verification failures (exit 1).
|
||||
usage_error() {
|
||||
echo "Error: $*" >&2
|
||||
echo "Usage: pr-diff.sh -n <pr_number> [-r owner/repo] [--host host] [-o <output_file>] (see --help)" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-n|--number)
|
||||
[[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
|
||||
PR_NUMBER="$2"
|
||||
shift 2
|
||||
;;
|
||||
-o|--output)
|
||||
[[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
|
||||
OUTPUT_FILE="$2"
|
||||
shift 2
|
||||
;;
|
||||
-r|--repo)
|
||||
[[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
|
||||
REPO_OVERRIDE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--host)
|
||||
[[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
|
||||
HOST_OVERRIDE="$2"
|
||||
shift 2
|
||||
;;
|
||||
@@ -40,18 +52,18 @@ while [[ $# -gt 0 ]]; do
|
||||
echo " --host Gitea host for --repo API calls (or set GITEA_HOST/GITEA_URL)"
|
||||
echo " -o, --output Output file (optional, prints to stdout if omitted)"
|
||||
echo " -h, --help Show this help"
|
||||
echo ""
|
||||
echo "Exit codes: 0 success; 2 usage error (stderr); 1 provider/credential failure."
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
exit 1
|
||||
usage_error "unknown option: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$PR_NUMBER" ]]; then
|
||||
echo "Error: PR number is required (-n)" >&2
|
||||
exit 1
|
||||
usage_error "PR number is required (-n/--number)"
|
||||
fi
|
||||
|
||||
if [[ -n "$REPO_OVERRIDE" ]]; then
|
||||
|
||||
@@ -34,29 +34,41 @@ Examples:
|
||||
$(basename "$0") -s merged -a username # Merged PRs by user
|
||||
$(basename "$0") --repo ddk/ai-bma # List PRs from anywhere
|
||||
EOF
|
||||
exit "${1:-1}"
|
||||
exit "${1:-2}"
|
||||
}
|
||||
|
||||
# Parse arguments
|
||||
# Usage-error contract (R4): usage errors print to STDERR and exit 2,
|
||||
# distinct from provider, credential, and verification failures (exit 1).
|
||||
usage_error() {
|
||||
echo "Error: $*" >&2
|
||||
usage >&2
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-s|--state)
|
||||
[[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
|
||||
STATE="$2"
|
||||
shift 2
|
||||
;;
|
||||
-l|--label)
|
||||
[[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
|
||||
LABEL="$2"
|
||||
shift 2
|
||||
;;
|
||||
-a|--author)
|
||||
[[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
|
||||
AUTHOR="$2"
|
||||
shift 2
|
||||
;;
|
||||
-n|--limit)
|
||||
[[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
|
||||
LIMIT="$2"
|
||||
shift 2
|
||||
;;
|
||||
-r|--repo)
|
||||
[[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
|
||||
REPO_OVERRIDE="$2"
|
||||
shift 2
|
||||
;;
|
||||
@@ -64,8 +76,7 @@ while [[ $# -gt 0 ]]; do
|
||||
usage 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
usage
|
||||
usage_error "unknown option: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
@@ -12,13 +12,23 @@ source "$SCRIPT_DIR/detect-platform.sh"
|
||||
PR_NUMBER=""
|
||||
OUTPUT_FILE=""
|
||||
|
||||
# Usage-error contract (R4): usage errors print to STDERR and exit 2,
|
||||
# distinct from provider, credential, and verification failures (exit 1).
|
||||
usage_error() {
|
||||
echo "Error: $*" >&2
|
||||
echo "Usage: pr-metadata.sh -n <pr_number> [-o <output_file>] (see --help)" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-n|--number)
|
||||
[[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
|
||||
PR_NUMBER="$2"
|
||||
shift 2
|
||||
;;
|
||||
-o|--output)
|
||||
[[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
|
||||
OUTPUT_FILE="$2"
|
||||
shift 2
|
||||
;;
|
||||
@@ -29,18 +39,18 @@ while [[ $# -gt 0 ]]; do
|
||||
echo " -n, --number PR number (required)"
|
||||
echo " -o, --output Output file (optional, prints to stdout if omitted)"
|
||||
echo " -h, --help Show this help"
|
||||
echo ""
|
||||
echo "Exit codes: 0 success; 2 usage error (stderr); 1 provider/credential failure."
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
exit 1
|
||||
usage_error "unknown option: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$PR_NUMBER" ]]; then
|
||||
echo "Error: PR number is required (-n)" >&2
|
||||
exit 1
|
||||
usage_error "PR number is required (-n/--number)"
|
||||
fi
|
||||
|
||||
write_metadata() {
|
||||
|
||||
@@ -11,13 +11,23 @@ source "$SCRIPT_DIR/detect-platform.sh"
|
||||
PR_NUMBER=""
|
||||
REPO_OVERRIDE=""
|
||||
|
||||
# Usage-error contract (R4): usage errors print to STDERR and exit 2,
|
||||
# distinct from provider, credential, and verification failures (exit 1).
|
||||
usage_error() {
|
||||
echo "Error: $*" >&2
|
||||
echo "Usage: pr-view.sh -n <pr_number> [-r owner/repo] (see --help)" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-n|--number)
|
||||
[[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
|
||||
PR_NUMBER="$2"
|
||||
shift 2
|
||||
;;
|
||||
-r|--repo)
|
||||
[[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
|
||||
REPO_OVERRIDE="$2"
|
||||
shift 2
|
||||
;;
|
||||
@@ -28,18 +38,18 @@ while [[ $# -gt 0 ]]; do
|
||||
echo " -n, --number PR number (required)"
|
||||
echo " -r, --repo Repository slug (default: infer from git origin)"
|
||||
echo " -h, --help Show this help"
|
||||
echo ""
|
||||
echo "Exit codes: 0 success; 2 usage error (stderr); 1 provider/credential failure."
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
exit 1
|
||||
usage_error "unknown option: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$PR_NUMBER" ]]; then
|
||||
echo "Error: PR number is required (-n)"
|
||||
exit 1
|
||||
usage_error "PR number is required (-n/--number)"
|
||||
fi
|
||||
|
||||
if [[ -n "$REPO_OVERRIDE" ]]; then
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env bash
|
||||
# Usage-error contract for pr-diff.sh (R4, 2026-08-28).
|
||||
#
|
||||
# issue-edit already uses long-flag-first parsing (-i/--issue, -t/--title,
|
||||
# -b/--body, -l/--labels, -m/--milestone); this adds the rc=2 usage-error
|
||||
# contract, value checks, and the no-provider-contact proof. Required: -i.
|
||||
#
|
||||
# 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. A value-less flag (-i -b -c and long forms) exits 2 (stderr).
|
||||
# 5. -b and -c both pass parsing (sandboxed runner: the run then fails
|
||||
# at credential resolution, nonzero and NOT 2) — no real token is
|
||||
# ever read and no provider is contacted.
|
||||
# 6. No arm performs any provider request (PATH shims record every
|
||||
# invocation; 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/pr-diff-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"
|
||||
|
||||
# Unlike the issue suites, these stubs FAIL (exit 99): pr-close has an
|
||||
# API fallback that treats a successful curl as a closed PR, so exit-0
|
||||
# stubs would let the sandbox arms "succeed" (measured 2026-08-28).
|
||||
for tool in gh tea curl; do
|
||||
cat > "$BIN_DIR/$tool" <<STUB
|
||||
#!/usr/bin/env bash
|
||||
echo "$tool \$*" >> "$PROBE_LOG"
|
||||
exit 99
|
||||
STUB
|
||||
chmod +x "$BIN_DIR/$tool"
|
||||
done
|
||||
|
||||
run_wrapper() {
|
||||
( cd "$WORK_DIR" && PATH="$BIN_DIR:$PATH" "$SCRIPT_DIR/pr-diff.sh" "$@" )
|
||||
}
|
||||
|
||||
# Hermetic variant: neutralizes every identity/credential source the wrapper
|
||||
# consults so parse-acceptance arms fail at credential resolution in ANY cwd
|
||||
# repo (see test-issue-comment-usage-contract.sh for the measured incident).
|
||||
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/pr-diff.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 "$2: stderr missing '$1'"
|
||||
}
|
||||
|
||||
# 1. Help exits 0 and prints usage.
|
||||
expect_rc 0 "--help exits 0" --help
|
||||
grep -q "Usage: pr-diff.sh" "$OUT_FILE" || fail "--help did not print usage"
|
||||
expect_rc 0 "-h exits 0" -h
|
||||
|
||||
# 2. Unknown option: rc 2, stderr.
|
||||
expect_rc 2 "unknown option exits 2" --bogus
|
||||
expect_stderr "[Uu]nknown option" "unknown option names itself on stderr"
|
||||
|
||||
# 3. Missing required PR number: rc 2, stderr.
|
||||
expect_rc 2 "missing -n exits 2"
|
||||
expect_stderr "PR number is required" "missing -n message on stderr"
|
||||
|
||||
|
||||
# 4. Value-less flags: rc 2 with "requires a value" on stderr.
|
||||
for flag in -n -o -r --number --output --repo --host; do
|
||||
expect_rc 2 "value-less $flag exits 2" "$flag"
|
||||
expect_stderr "requires a value" "value-less $flag message on stderr"
|
||||
done
|
||||
|
||||
# 4a. An option-like value is a MISSING value, not a value (codex PR #1464:
|
||||
# -b --help previously consumed --help as the body and performed the write).
|
||||
expect_rc 2 "option-like value rejected" -n --help
|
||||
expect_rc 2 "short flag value rejected" -n -h
|
||||
expect_stderr "requires a value" "short flag value message on stderr"
|
||||
expect_stderr "requires a value" "option-like value message on stderr"
|
||||
|
||||
# 4b. Parser-failure arms (1-4) must have performed ZERO provider contact.
|
||||
if [[ -s "$PROBE_LOG" ]]; then
|
||||
echo "FAIL: a parser-failure arm contacted a provider:" >&2
|
||||
cat "$PROBE_LOG" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 5. Alias acceptance under the sandbox: both -b and -c carry a value past
|
||||
# parsing; the run fails at credential resolution nonzero and NOT 2.
|
||||
for flag in -n; do
|
||||
rc=0
|
||||
run_wrapper_sandboxed -n 5 >"$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
|
||||
|
||||
# 6. Post-sandbox provider assertions are intentionally NOT applied here:
|
||||
# pr-close's gitea path attempts a tea WRITE (tea pr comment) when a
|
||||
# comment parses, then falls back to the API. Hermeticity for this
|
||||
# wrapper comes from the FAILING stubs (exit 99), not from non-contact —
|
||||
# the arm above proves only parse acceptance and non-usage classification.
|
||||
# Parser-failure arms (1-4) remain zero-contact (asserted at 4b).
|
||||
|
||||
echo "pr-diff.sh usage-contract regression passed (R1/R4)"
|
||||
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env bash
|
||||
# Usage-error contract for pr-list.sh (R4, 2026-08-28).
|
||||
#
|
||||
# issue-edit already uses long-flag-first parsing (-i/--issue, -t/--title,
|
||||
# -b/--body, -l/--labels, -m/--milestone); this adds the rc=2 usage-error
|
||||
# contract, value checks, and the no-provider-contact proof. Required: -i.
|
||||
#
|
||||
# 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. A value-less flag (-i -b -c and long forms) exits 2 (stderr).
|
||||
# 5. -b and -c both pass parsing (sandboxed runner: the run then fails
|
||||
# at credential resolution, nonzero and NOT 2) — no real token is
|
||||
# ever read and no provider is contacted.
|
||||
# 6. No arm performs any provider request (PATH shims record every
|
||||
# invocation; 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/pr-list-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"
|
||||
|
||||
# Unlike the issue suites, these stubs FAIL (exit 99): pr-close has an
|
||||
# API fallback that treats a successful curl as a closed PR, so exit-0
|
||||
# stubs would let the sandbox arms "succeed" (measured 2026-08-28).
|
||||
for tool in gh tea curl; do
|
||||
cat > "$BIN_DIR/$tool" <<STUB
|
||||
#!/usr/bin/env bash
|
||||
echo "$tool \$*" >> "$PROBE_LOG"
|
||||
exit 99
|
||||
STUB
|
||||
chmod +x "$BIN_DIR/$tool"
|
||||
done
|
||||
|
||||
run_wrapper() {
|
||||
( cd "$WORK_DIR" && PATH="$BIN_DIR:$PATH" "$SCRIPT_DIR/pr-list.sh" "$@" )
|
||||
}
|
||||
|
||||
# Hermetic variant: neutralizes every identity/credential source the wrapper
|
||||
# consults so parse-acceptance arms fail at credential resolution in ANY cwd
|
||||
# repo (see test-issue-comment-usage-contract.sh for the measured incident).
|
||||
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/pr-list.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 "$2: stderr missing '$1'"
|
||||
}
|
||||
|
||||
# 1. Help exits 0 and prints usage.
|
||||
expect_rc 0 "--help exits 0" --help
|
||||
grep -q "Usage: pr-list.sh" "$OUT_FILE" || fail "--help did not print usage"
|
||||
expect_rc 0 "-h exits 0" -h
|
||||
|
||||
# 2. Unknown option: rc 2, stderr.
|
||||
expect_rc 2 "unknown option exits 2" --bogus
|
||||
expect_stderr "[Uu]nknown option" "unknown option names itself on stderr"
|
||||
|
||||
# 3. Missing required PR number: rc 2, stderr.
|
||||
|
||||
|
||||
|
||||
# 4. Value-less flags: rc 2 with "requires a value" on stderr.
|
||||
for flag in -s -l -a -n -r --state --label --author --limit --repo; do
|
||||
expect_rc 2 "value-less $flag exits 2" "$flag"
|
||||
expect_stderr "requires a value" "value-less $flag message on stderr"
|
||||
done
|
||||
|
||||
# 4a. An option-like value is a MISSING value, not a value (codex PR #1464:
|
||||
# -b --help previously consumed --help as the body and performed the write).
|
||||
expect_rc 2 "option-like value rejected" -s --help
|
||||
expect_rc 2 "short flag value rejected" -s -h
|
||||
expect_stderr "requires a value" "short flag value message on stderr"
|
||||
expect_stderr "requires a value" "option-like value message on stderr"
|
||||
|
||||
# 4b. Parser-failure arms (1-4) must have performed ZERO provider contact.
|
||||
if [[ -s "$PROBE_LOG" ]]; then
|
||||
echo "FAIL: a parser-failure arm contacted a provider:" >&2
|
||||
cat "$PROBE_LOG" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 5. Alias acceptance under the sandbox: both -b and -c carry a value past
|
||||
# parsing; the run fails at credential resolution nonzero and NOT 2.
|
||||
for flag in -s; do
|
||||
rc=0
|
||||
run_wrapper_sandboxed -s open >"$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
|
||||
|
||||
# 6. Post-sandbox provider assertions are intentionally NOT applied here:
|
||||
# pr-close's gitea path attempts a tea WRITE (tea pr comment) when a
|
||||
# comment parses, then falls back to the API. Hermeticity for this
|
||||
# wrapper comes from the FAILING stubs (exit 99), not from non-contact —
|
||||
# the arm above proves only parse acceptance and non-usage classification.
|
||||
# Parser-failure arms (1-4) remain zero-contact (asserted at 4b).
|
||||
|
||||
echo "pr-list.sh usage-contract regression passed (R1/R4)"
|
||||
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env bash
|
||||
# Usage-error contract for pr-metadata.sh (R4, 2026-08-28).
|
||||
#
|
||||
# issue-edit already uses long-flag-first parsing (-i/--issue, -t/--title,
|
||||
# -b/--body, -l/--labels, -m/--milestone); this adds the rc=2 usage-error
|
||||
# contract, value checks, and the no-provider-contact proof. Required: -i.
|
||||
#
|
||||
# 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. A value-less flag (-i -b -c and long forms) exits 2 (stderr).
|
||||
# 5. -b and -c both pass parsing (sandboxed runner: the run then fails
|
||||
# at credential resolution, nonzero and NOT 2) — no real token is
|
||||
# ever read and no provider is contacted.
|
||||
# 6. No arm performs any provider request (PATH shims record every
|
||||
# invocation; 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/pr-metadata-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"
|
||||
|
||||
# Unlike the issue suites, these stubs FAIL (exit 99): pr-close has an
|
||||
# API fallback that treats a successful curl as a closed PR, so exit-0
|
||||
# stubs would let the sandbox arms "succeed" (measured 2026-08-28).
|
||||
for tool in gh tea curl; do
|
||||
cat > "$BIN_DIR/$tool" <<STUB
|
||||
#!/usr/bin/env bash
|
||||
echo "$tool \$*" >> "$PROBE_LOG"
|
||||
exit 99
|
||||
STUB
|
||||
chmod +x "$BIN_DIR/$tool"
|
||||
done
|
||||
|
||||
run_wrapper() {
|
||||
( cd "$WORK_DIR" && PATH="$BIN_DIR:$PATH" "$SCRIPT_DIR/pr-metadata.sh" "$@" )
|
||||
}
|
||||
|
||||
# Hermetic variant: neutralizes every identity/credential source the wrapper
|
||||
# consults so parse-acceptance arms fail at credential resolution in ANY cwd
|
||||
# repo (see test-issue-comment-usage-contract.sh for the measured incident).
|
||||
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/pr-metadata.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 "$2: stderr missing '$1'"
|
||||
}
|
||||
|
||||
# 1. Help exits 0 and prints usage.
|
||||
expect_rc 0 "--help exits 0" --help
|
||||
grep -q "Usage: pr-metadata.sh" "$OUT_FILE" || fail "--help did not print usage"
|
||||
expect_rc 0 "-h exits 0" -h
|
||||
|
||||
# 2. Unknown option: rc 2, stderr.
|
||||
expect_rc 2 "unknown option exits 2" --bogus
|
||||
expect_stderr "[Uu]nknown option" "unknown option names itself on stderr"
|
||||
|
||||
# 3. Missing required PR number: rc 2, stderr.
|
||||
expect_rc 2 "missing -n exits 2"
|
||||
expect_stderr "PR number is required" "missing -n message on stderr"
|
||||
|
||||
|
||||
# 4. Value-less flags: rc 2 with "requires a value" on stderr.
|
||||
for flag in -n -o --number --output; do
|
||||
expect_rc 2 "value-less $flag exits 2" "$flag"
|
||||
expect_stderr "requires a value" "value-less $flag message on stderr"
|
||||
done
|
||||
|
||||
# 4a. An option-like value is a MISSING value, not a value (codex PR #1464:
|
||||
# -b --help previously consumed --help as the body and performed the write).
|
||||
expect_rc 2 "option-like value rejected" -n --help
|
||||
expect_rc 2 "short flag value rejected" -n -h
|
||||
expect_stderr "requires a value" "short flag value message on stderr"
|
||||
expect_stderr "requires a value" "option-like value message on stderr"
|
||||
|
||||
# 4b. Parser-failure arms (1-4) must have performed ZERO provider contact.
|
||||
if [[ -s "$PROBE_LOG" ]]; then
|
||||
echo "FAIL: a parser-failure arm contacted a provider:" >&2
|
||||
cat "$PROBE_LOG" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 5. Alias acceptance under the sandbox: both -b and -c carry a value past
|
||||
# parsing; the run fails at credential resolution nonzero and NOT 2.
|
||||
for flag in -n; do
|
||||
rc=0
|
||||
run_wrapper_sandboxed -n 5 >"$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
|
||||
|
||||
# 6. Post-sandbox provider assertions are intentionally NOT applied here:
|
||||
# pr-close's gitea path attempts a tea WRITE (tea pr comment) when a
|
||||
# comment parses, then falls back to the API. Hermeticity for this
|
||||
# wrapper comes from the FAILING stubs (exit 99), not from non-contact —
|
||||
# the arm above proves only parse acceptance and non-usage classification.
|
||||
# Parser-failure arms (1-4) remain zero-contact (asserted at 4b).
|
||||
|
||||
echo "pr-metadata.sh usage-contract regression passed (R1/R4)"
|
||||
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env bash
|
||||
# Usage-error contract for pr-view.sh (R4, 2026-08-28).
|
||||
#
|
||||
# issue-edit already uses long-flag-first parsing (-i/--issue, -t/--title,
|
||||
# -b/--body, -l/--labels, -m/--milestone); this adds the rc=2 usage-error
|
||||
# contract, value checks, and the no-provider-contact proof. Required: -i.
|
||||
#
|
||||
# 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. A value-less flag (-i -b -c and long forms) exits 2 (stderr).
|
||||
# 5. -b and -c both pass parsing (sandboxed runner: the run then fails
|
||||
# at credential resolution, nonzero and NOT 2) — no real token is
|
||||
# ever read and no provider is contacted.
|
||||
# 6. No arm performs any provider request (PATH shims record every
|
||||
# invocation; 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/pr-view-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"
|
||||
|
||||
# Unlike the issue suites, these stubs FAIL (exit 99): pr-close has an
|
||||
# API fallback that treats a successful curl as a closed PR, so exit-0
|
||||
# stubs would let the sandbox arms "succeed" (measured 2026-08-28).
|
||||
for tool in gh tea curl; do
|
||||
cat > "$BIN_DIR/$tool" <<STUB
|
||||
#!/usr/bin/env bash
|
||||
echo "$tool \$*" >> "$PROBE_LOG"
|
||||
exit 99
|
||||
STUB
|
||||
chmod +x "$BIN_DIR/$tool"
|
||||
done
|
||||
|
||||
run_wrapper() {
|
||||
( cd "$WORK_DIR" && PATH="$BIN_DIR:$PATH" "$SCRIPT_DIR/pr-view.sh" "$@" )
|
||||
}
|
||||
|
||||
# Hermetic variant: neutralizes every identity/credential source the wrapper
|
||||
# consults so parse-acceptance arms fail at credential resolution in ANY cwd
|
||||
# repo (see test-issue-comment-usage-contract.sh for the measured incident).
|
||||
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/pr-view.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 "$2: stderr missing '$1'"
|
||||
}
|
||||
|
||||
# 1. Help exits 0 and prints usage.
|
||||
expect_rc 0 "--help exits 0" --help
|
||||
grep -q "Usage: pr-view.sh" "$OUT_FILE" || fail "--help did not print usage"
|
||||
expect_rc 0 "-h exits 0" -h
|
||||
|
||||
# 2. Unknown option: rc 2, stderr.
|
||||
expect_rc 2 "unknown option exits 2" --bogus
|
||||
expect_stderr "[Uu]nknown option" "unknown option names itself on stderr"
|
||||
|
||||
# 3. Missing required PR number: rc 2, stderr.
|
||||
expect_rc 2 "missing -n exits 2"
|
||||
expect_stderr "PR number is required" "missing -n message on stderr"
|
||||
|
||||
|
||||
# 4. Value-less flags: rc 2 with "requires a value" on stderr.
|
||||
for flag in -n -r --number --repo; do
|
||||
expect_rc 2 "value-less $flag exits 2" "$flag"
|
||||
expect_stderr "requires a value" "value-less $flag message on stderr"
|
||||
done
|
||||
|
||||
# 4a. An option-like value is a MISSING value, not a value (codex PR #1464:
|
||||
# -b --help previously consumed --help as the body and performed the write).
|
||||
expect_rc 2 "option-like value rejected" -n --help
|
||||
expect_rc 2 "short flag value rejected" -n -h
|
||||
expect_stderr "requires a value" "short flag value message on stderr"
|
||||
expect_stderr "requires a value" "option-like value message on stderr"
|
||||
|
||||
# 4b. Parser-failure arms (1-4) must have performed ZERO provider contact.
|
||||
if [[ -s "$PROBE_LOG" ]]; then
|
||||
echo "FAIL: a parser-failure arm contacted a provider:" >&2
|
||||
cat "$PROBE_LOG" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 5. Alias acceptance under the sandbox: both -b and -c carry a value past
|
||||
# parsing; the run fails at credential resolution nonzero and NOT 2.
|
||||
for flag in -n; do
|
||||
rc=0
|
||||
run_wrapper_sandboxed -n 5 >"$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
|
||||
|
||||
# 6. Post-sandbox provider assertions are intentionally NOT applied here:
|
||||
# pr-close's gitea path attempts a tea WRITE (tea pr comment) when a
|
||||
# comment parses, then falls back to the API. Hermeticity for this
|
||||
# wrapper comes from the FAILING stubs (exit 99), not from non-contact —
|
||||
# the arm above proves only parse acceptance and non-usage classification.
|
||||
# Parser-failure arms (1-4) remain zero-contact (asserted at 4b).
|
||||
|
||||
echo "pr-view.sh usage-contract regression passed (R1/R4)"
|
||||
Reference in New Issue
Block a user