Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ceb11058f | ||
|
|
abe9dbb5c1 |
@@ -46,10 +46,6 @@ steps:
|
||||
# [0] of the pnpm chain, so severing that chain would silence it together
|
||||
# with everything it guards; this direct line keeps one instrument running.
|
||||
- bash packages/mosaic/framework/tools/quality/scripts/check-test-enumeration.sh
|
||||
# Hermetic regression for issue-close.sh (#1081): mocks tea/curl onto PATH
|
||||
# and sandboxes a throwaway git repo, so it resolves no real credentials and
|
||||
# joins CI directly rather than the exclusions file.
|
||||
- bash packages/mosaic/framework/tools/git/test-issue-close-fail-closed.sh
|
||||
|
||||
# Blocking gate (#791): a framework upgrade must never write or delete an
|
||||
# operator-owned path. The HARD GATE proves an unanticipated operator sentinel
|
||||
|
||||
@@ -245,21 +245,9 @@ describe('EnrollmentService.createToken', () => {
|
||||
const after = Date.now();
|
||||
|
||||
const expiresMs = new Date(result.expiresAt).getTime();
|
||||
|
||||
// The property under test is CLAMPING: a 9999s request must come back as 900s.
|
||||
// The gap between clamped and unclamped is 9_099_000 ms, so the tolerance below
|
||||
// only has to exceed CI scheduling jitter — it does not need to be tight to keep
|
||||
// the assertion discriminating. A 5s allowance consumes 0.05% of that margin and
|
||||
// an unclamped result still misses by three orders of magnitude.
|
||||
//
|
||||
// It was 100ms and failed on a loaded agent at 900_106 — 6ms over (#1090). A
|
||||
// wall-clock budget sized to a fast machine is a flake, not a tighter test.
|
||||
const CI_JITTER_MS = 5_000;
|
||||
expect(expiresMs - before).toBeLessThanOrEqual(900_000 + CI_JITTER_MS);
|
||||
// Should be at most 900s from now
|
||||
expect(expiresMs - before).toBeLessThanOrEqual(900_000 + 100);
|
||||
expect(expiresMs - after).toBeGreaterThanOrEqual(0);
|
||||
// Explicitly pin the clamp itself, independent of any timing allowance:
|
||||
// unclamped (9999s) would exceed this by ~9_099_000 ms.
|
||||
expect(expiresMs - before).toBeLessThan(1_000_000);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -83,17 +83,3 @@ Add an optional, identity-checked Gitea squash message to `pr-merge.sh` so genui
|
||||
## Current hold point
|
||||
|
||||
PR `mosaicstack/stack#1066` is open. Its first frozen head `f4b162fa…` was terminal-green in Woodpecker `mosaic` pipeline `#2225`, but that evidence becomes stale when the canonical port moves the head. The deployed wrapper remains baseline `08a65e85…`; no manual copy will occur. Canonical port tests, commit amendment, rebase, one guarded force-with-lease, exact-head CI, and new independent review remain. Even after source merge, activation remains blocked on an assigned package-release/reseed owner and installed-byte read-back.
|
||||
|
||||
## Security review 96 remediation
|
||||
|
||||
Exact reviewed predecessor head: `1ceb11058f64dd7f4a817ceb2124f980a1c4dd23`.
|
||||
|
||||
RED-first focused harness produced 10 named failures: all curl calls lacked size/time/connect bounds; raw ESC email reached mutation; oversized and stalled curl failures were discarded and reached mutation; nonempty Basic output with resolver rc 91 authorized mutation.
|
||||
|
||||
Security remediation:
|
||||
|
||||
- Removed the cross-principal HTTP-401 Basic fallback. Both inspection-401 and merge-401 paths now refuse without Basic resolution or mutation; `get_gitea_basic_auth` references in the merge subject are 0.
|
||||
- Applied `--max-filesize`, `--max-time`, and `--connect-timeout` to all 3/3 provider curl sites and fail closed on curl transport rc at all 3/3 sites.
|
||||
- Required linked email bytes to be ASCII and printable before constructing `MergeMessageField`; guarded construction sites 1/1.
|
||||
|
||||
GREEN: message-field, exact-head, empty-UID/API, queue branch/repository/SHA, bash syntax, ShellCheck, and diff check pass. R7 total-removal mutants went RED: email guard 3 rows; bound switches 1 row; transport-rc guards 4 rows; HTTP-401 refusal 3 rows. R7 bound: mutants prove total removal only; explicit denominators above prove site coverage.
|
||||
|
||||
@@ -5,10 +5,7 @@
|
||||
|
||||
detect_platform() {
|
||||
local remote_url
|
||||
# `|| true` is load-bearing under `set -e`: outside a git repo this returns 128 and
|
||||
# kills the CALLER before the -z check below can run, so the error message that is
|
||||
# already written here was unreachable. Same idiom as get_gitea_repo_args() below.
|
||||
remote_url=$(git remote get-url origin 2>/dev/null) || true
|
||||
remote_url=$(git remote get-url origin 2>/dev/null)
|
||||
|
||||
if [[ -z "$remote_url" ]]; then
|
||||
echo "error: not a git repository or no origin remote" >&2
|
||||
@@ -42,10 +39,7 @@ detect_platform() {
|
||||
|
||||
get_repo_info() {
|
||||
local remote_url
|
||||
# `|| true` is load-bearing under `set -e`: outside a git repo this returns 128 and
|
||||
# kills the CALLER before the -z check below can run, so the error message that is
|
||||
# already written here was unreachable. Same idiom as get_gitea_repo_args() below.
|
||||
remote_url=$(git remote get-url origin 2>/dev/null) || true
|
||||
remote_url=$(git remote get-url origin 2>/dev/null)
|
||||
|
||||
if [[ -z "$remote_url" ]]; then
|
||||
echo "error: not a git repository or no origin remote" >&2
|
||||
@@ -246,21 +240,6 @@ PY
|
||||
} >&2
|
||||
}
|
||||
|
||||
# Explain tea's most misleading failure. `user does not exist [uid: 0, name: ]` reads
|
||||
# as a missing account; it almost always means a REVOKED OR STALE TOKEN. `tea login`
|
||||
# keeps its OWN COPY of the token, so rotating the credential store does not update it.
|
||||
# Diagnostic only -- stderr, no control flow, no exit.
|
||||
explain_tea_user_does_not_exist() {
|
||||
cat >&2 <<'MSG'
|
||||
NOTE: `user does not exist [uid: 0, name: ]` from tea usually means a REVOKED OR STALE TOKEN,
|
||||
not a missing account. A `tea login` stores its OWN COPY of the token; rotating the
|
||||
credential store does NOT update it.
|
||||
CHECK: the login's cached copy (`tea login list` -- read the FULL table, never `| head`),
|
||||
then re-register that login against the current token.
|
||||
DO NOT probe capability with a mutating request; a POST is the action, not a check.
|
||||
MSG
|
||||
}
|
||||
|
||||
get_gitea_login_for_host() {
|
||||
local host="${1:-}"
|
||||
local login
|
||||
|
||||
@@ -91,32 +91,13 @@ elif [[ "$PLATFORM" == "gitea" ]]; then
|
||||
GITEA_LOGIN_NAME=$(get_gitea_login || true)
|
||||
if [[ -n "$GITEA_LOGIN_NAME" ]]; then
|
||||
if [[ -n "$COMMENT" ]]; then
|
||||
# `tea issue comment` is NOT a subcommand -- tea 0.11.x lists only
|
||||
# list/create/edit/reopen/close under `tea issue`. Comments are the
|
||||
# TOP-LEVEL `tea comment`, which takes the same --repo/--login flags.
|
||||
# The old call therefore always failed, was unchecked, and the script
|
||||
# closed the issue anyway, losing the record of WHY.
|
||||
#
|
||||
# Use `tea comment` rather than the API helper so the comment and the
|
||||
# close are made by the SAME principal ($GITEA_LOGIN_NAME). Routing the
|
||||
# comment through the token-authenticated helper here would attribute the
|
||||
# comment to the token holder and the close to the tea login -- two
|
||||
# principals for one operation.
|
||||
tea comment "$ISSUE_NUMBER" "$COMMENT" --repo "$OWNER/$REPO" --login "$GITEA_LOGIN_NAME" || {
|
||||
echo "Error: failed to post comment on #$ISSUE_NUMBER -- NOT closing (fail closed)." >&2
|
||||
exit 1
|
||||
}
|
||||
tea issue comment "$ISSUE_NUMBER" "$COMMENT" --repo "$OWNER/$REPO" --login "$GITEA_LOGIN_NAME"
|
||||
fi
|
||||
tea issue close "$ISSUE_NUMBER" --repo "$OWNER/$REPO" --login "$GITEA_LOGIN_NAME"
|
||||
else
|
||||
echo "No tea login configured for $(get_remote_host); using authenticated Gitea API fallback." >&2
|
||||
if [[ -n "$COMMENT" ]]; then
|
||||
# Fail closed here too: an unchecked comment lets the issue close without its
|
||||
# audit trail, which is the same defect as the tea path above.
|
||||
gitea_issue_comment_api || {
|
||||
echo "Error: failed to post comment on #$ISSUE_NUMBER -- NOT closing (fail closed)." >&2
|
||||
exit 1
|
||||
}
|
||||
gitea_issue_comment_api
|
||||
fi
|
||||
gitea_issue_close_api
|
||||
fi
|
||||
|
||||
@@ -156,7 +156,6 @@ case "$PLATFORM" in
|
||||
exit 0
|
||||
fi
|
||||
echo "Warning: tea issue create failed, trying Gitea API fallback..." >&2
|
||||
{ declare -F explain_tea_user_does_not_exist >/dev/null && explain_tea_user_does_not_exist; } || true
|
||||
fi
|
||||
gitea_issue_create_api
|
||||
;;
|
||||
|
||||
@@ -71,7 +71,6 @@ elif [[ "$PLATFORM" == "gitea" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
echo "Warning: tea issue view failed, trying Gitea API fallback..." >&2
|
||||
{ declare -F explain_tea_user_does_not_exist >/dev/null && explain_tea_user_does_not_exist; } || true
|
||||
fi
|
||||
gitea_issue_view_api
|
||||
else
|
||||
|
||||
@@ -219,7 +219,6 @@ case "$PLATFORM" in
|
||||
exit 0
|
||||
fi
|
||||
echo "Warning: tea pr create failed, trying Gitea API fallback..." >&2
|
||||
{ declare -F explain_tea_user_does_not_exist >/dev/null && explain_tea_user_does_not_exist; } || true
|
||||
gitea_pr_create_api
|
||||
;;
|
||||
*)
|
||||
|
||||
@@ -178,20 +178,6 @@ else:
|
||||
LAST_GITEA_HTTP_CODE="000"
|
||||
LAST_GITEA_ERROR=""
|
||||
MERGE_TEMP_DIRS=()
|
||||
GITEA_CURL_MAX_BYTES="${MOSAIC_GITEA_CURL_MAX_BYTES:-1048576}"
|
||||
GITEA_CURL_MAX_TIME="${MOSAIC_GITEA_CURL_MAX_TIME_SEC:-30}"
|
||||
GITEA_CURL_CONNECT_TIMEOUT="${MOSAIC_GITEA_CURL_CONNECT_TIMEOUT_SEC:-10}"
|
||||
for bound in "$GITEA_CURL_MAX_BYTES" "$GITEA_CURL_MAX_TIME" "$GITEA_CURL_CONNECT_TIMEOUT"; do
|
||||
if [[ ! "$bound" =~ ^[1-9][0-9]*$ ]]; then
|
||||
echo "Error: Gitea curl bounds must be positive integers; refusing request." >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
GITEA_CURL_BOUNDS=(
|
||||
--max-filesize "$GITEA_CURL_MAX_BYTES"
|
||||
--max-time "$GITEA_CURL_MAX_TIME"
|
||||
--connect-timeout "$GITEA_CURL_CONNECT_TIMEOUT"
|
||||
)
|
||||
|
||||
format_gitea_error_response() {
|
||||
local response_file="$1"
|
||||
@@ -233,7 +219,7 @@ trap 'exit 143' TERM
|
||||
|
||||
fetch_gitea_pr_head() {
|
||||
local host="$1" auth_mode="$2" credential="$3" work_root="$4"
|
||||
local response_file raw_code api_url auth_config curl_rc
|
||||
local response_file raw_code api_url auth_config
|
||||
response_file=$(mktemp "$work_root/pr-merge-pr.XXXXXX")
|
||||
api_url="https://${host}/api/v1/repos/${OWNER}/${REPO}/pulls/${PR_NUMBER}"
|
||||
if ! auth_config=$(write_curl_auth_config "$auth_mode" "$credential"); then
|
||||
@@ -241,15 +227,10 @@ fetch_gitea_pr_head() {
|
||||
rm -f "$response_file"
|
||||
return 1
|
||||
fi
|
||||
raw_code=$(curl -sS -K - "${GITEA_CURL_BOUNDS[@]}" -w '%{http_code}' -o "$response_file" \
|
||||
-H "User-Agent: curl/8" "$api_url" <<<"$auth_config")
|
||||
curl_rc=$?
|
||||
raw_code=$(printf '%s\n' "$auth_config" | \
|
||||
curl -sS -K - -w '%{http_code}' -o "$response_file" \
|
||||
-H "User-Agent: curl/8" "$api_url" || true)
|
||||
LAST_GITEA_HTTP_CODE="${raw_code:-000}"
|
||||
if [[ "$curl_rc" -ne 0 ]]; then
|
||||
LAST_GITEA_ERROR="curl transport failed (rc=$curl_rc)"
|
||||
rm -f "$response_file"
|
||||
return 1
|
||||
fi
|
||||
if [[ ! "$raw_code" =~ ^2 ]]; then
|
||||
LAST_GITEA_ERROR=$(format_gitea_error_response "$response_file")
|
||||
rm -f "$response_file"
|
||||
@@ -278,7 +259,7 @@ PY
|
||||
|
||||
fetch_gitea_pr_commits() {
|
||||
local host="$1" auth_mode="$2" credential="$3" work_root="$4"
|
||||
local page page_file combined_file merged_file raw_code page_count api_url auth_config curl_rc
|
||||
local page page_file combined_file merged_file raw_code page_count api_url auth_config
|
||||
mkdir -p "$work_root"
|
||||
if ! auth_config=$(write_curl_auth_config "$auth_mode" "$credential"); then
|
||||
echo "Error: Could not construct Gitea authentication config; refusing request." >&2
|
||||
@@ -291,15 +272,10 @@ fetch_gitea_pr_commits() {
|
||||
while true; do
|
||||
page_file=$(mktemp "$work_root/pr-merge-commits-page.XXXXXX")
|
||||
api_url="https://${host}/api/v1/repos/${OWNER}/${REPO}/pulls/${PR_NUMBER}/commits?limit=50&page=${page}"
|
||||
raw_code=$(curl -sS -K - "${GITEA_CURL_BOUNDS[@]}" -w '%{http_code}' -o "$page_file" \
|
||||
-H "User-Agent: curl/8" "$api_url" <<<"$auth_config")
|
||||
curl_rc=$?
|
||||
raw_code=$(printf '%s\n' "$auth_config" | \
|
||||
curl -sS -K - -w '%{http_code}' -o "$page_file" \
|
||||
-H "User-Agent: curl/8" "$api_url" || true)
|
||||
LAST_GITEA_HTTP_CODE="${raw_code:-000}"
|
||||
if [[ "$curl_rc" -ne 0 ]]; then
|
||||
LAST_GITEA_ERROR="curl transport failed (rc=$curl_rc)"
|
||||
rm -f "$page_file" "$combined_file"
|
||||
return 1
|
||||
fi
|
||||
if [[ ! "$raw_code" =~ ^2 ]]; then
|
||||
LAST_GITEA_ERROR=$(format_gitea_error_response "$page_file")
|
||||
rm -f "$page_file" "$combined_file"
|
||||
@@ -423,12 +399,7 @@ for item in commits:
|
||||
file=sys.stderr,
|
||||
)
|
||||
raise SystemExit(75)
|
||||
if (
|
||||
not email.isascii()
|
||||
or not email.isprintable()
|
||||
or not re.fullmatch(r"[A-Za-z0-9_.-]+", login)
|
||||
or not re.fullmatch(r"[^<>\s]+@[^<>\s]+", email)
|
||||
):
|
||||
if not re.fullmatch(r"[A-Za-z0-9_.-]+", login) or not re.fullmatch(r"[^<>\s]+@[^<>\s]+", email):
|
||||
print(
|
||||
f"BLOCK: commit {sha!r} has unusable linked identity "
|
||||
f"author.login={login!r}, commit.author.email={email!r}; refusing merge; "
|
||||
@@ -474,7 +445,7 @@ PY
|
||||
|
||||
merge_gitea_api_attempt() {
|
||||
local host="$1" auth_mode="$2" credential="$3"
|
||||
local api_url attempt_dir body_file raw_code commits_file fields_file context_file head_file payload_file work_root attempt_rc auth_config curl_rc
|
||||
local api_url attempt_dir body_file raw_code commits_file fields_file context_file head_file payload_file work_root attempt_rc auth_config
|
||||
LAST_GITEA_HTTP_CODE="000"
|
||||
LAST_GITEA_ERROR=""
|
||||
api_url="https://${host}/api/v1/repos/${OWNER}/${REPO}/pulls/${PR_NUMBER}/merge"
|
||||
@@ -549,18 +520,12 @@ PY
|
||||
rm -f "$body_file" "$payload_file"
|
||||
return 1
|
||||
fi
|
||||
raw_code=$(curl -sS -K - "${GITEA_CURL_BOUNDS[@]}" -w '%{http_code}' -o "$body_file" \
|
||||
-X POST -H "User-Agent: curl/8" \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-binary "@$payload_file" "$api_url" <<<"$auth_config")
|
||||
curl_rc=$?
|
||||
raw_code=$(printf '%s\n' "$auth_config" | \
|
||||
curl -sS -K - -w '%{http_code}' -o "$body_file" \
|
||||
-X POST -H "User-Agent: curl/8" \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-binary "@$payload_file" "$api_url" || true)
|
||||
LAST_GITEA_HTTP_CODE="${raw_code:-000}"
|
||||
if [[ "$curl_rc" -ne 0 ]]; then
|
||||
LAST_GITEA_ERROR="curl transport failed (rc=$curl_rc)"
|
||||
rm -f "$body_file" "$payload_file"
|
||||
rm -rf -- "$attempt_dir"
|
||||
return 1
|
||||
fi
|
||||
if [[ ! "$raw_code" =~ ^2 ]]; then
|
||||
LAST_GITEA_ERROR=$(format_gitea_error_response "$body_file")
|
||||
fi
|
||||
@@ -570,7 +535,7 @@ PY
|
||||
}
|
||||
|
||||
merge_gitea_with_api() {
|
||||
local host="$1" token attempt_rc
|
||||
local host="$1" token basic_auth attempt_rc
|
||||
|
||||
if ! token=$(get_gitea_token "$host"); then
|
||||
echo "Error: Could not resolve the required Gitea token; refusing merge without changing principals." >&2
|
||||
@@ -589,10 +554,28 @@ merge_gitea_with_api() {
|
||||
return 75
|
||||
fi
|
||||
if [[ "$LAST_GITEA_HTTP_CODE" != "401" ]]; then
|
||||
echo "Error: Gitea API merge failed with the identity-bound token (HTTP ${LAST_GITEA_HTTP_CODE:-000}).${LAST_GITEA_ERROR:+ Provider response: $LAST_GITEA_ERROR}" >&2
|
||||
echo "Error: Gitea API merge failed with token credential (HTTP ${LAST_GITEA_HTTP_CODE:-000}).${LAST_GITEA_ERROR:+ Provider response: $LAST_GITEA_ERROR} Basic Auth fallback is allowed only after HTTP 401." >&2
|
||||
return 1
|
||||
fi
|
||||
echo "Error: Gitea API rejected the identity-bound token with HTTP 401; refusing cross-principal credential fallback." >&2
|
||||
echo "Token credential received HTTP 401; retrying inspection and merge with configured Basic Auth." >&2
|
||||
|
||||
basic_auth=$(get_gitea_basic_auth "$host" || true)
|
||||
if [[ -n "$basic_auth" ]]; then
|
||||
if merge_gitea_api_attempt "$host" basic "$basic_auth"; then
|
||||
return 0
|
||||
else
|
||||
attempt_rc=$?
|
||||
fi
|
||||
if [[ "$attempt_rc" -eq 75 ]]; then
|
||||
return 75
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "$token" && -z "$basic_auth" ]]; then
|
||||
echo "Error: No Gitea credential is available for the merge operation." >&2
|
||||
else
|
||||
echo "Error: Gitea API merge failed for all configured credentials (last HTTP ${LAST_GITEA_HTTP_CODE:-000}).${LAST_GITEA_ERROR:+ Provider response: $LAST_GITEA_ERROR}" >&2
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Regression: detect_platform / get_repo_info must FAIL LOUDLY outside a git repo,
|
||||
# not kill the caller silently.
|
||||
#
|
||||
# Both functions already contained the right error path:
|
||||
# if [[ -z "$remote_url" ]]; then echo "error: not a git repository..." >&2; return 1; fi
|
||||
# but under `set -e` -- which every wrapper in this directory uses -- the preceding
|
||||
# assignment `remote_url=$(git remote get-url origin 2>/dev/null)` returns git's 128
|
||||
# outside a repo and terminates the CALLER first. The message was unreachable.
|
||||
#
|
||||
# Observed cost: pr-review.sh invoked from a non-repo cwd exits 128 with NO stdout and
|
||||
# NO stderr, even when -r/--repo and -H/--host are supplied -- the flags documented as
|
||||
# "skips git-remote inference". Two reviewer seats hit this and correctly reported
|
||||
# `blocked` with no diagnostic to report.
|
||||
#
|
||||
# The control that matters is the LOUD one: asserting "rc != 0" passes on the broken
|
||||
# build too, because 128 is also non-zero. The test must assert the MESSAGE.
|
||||
set -uo pipefail
|
||||
fail=0
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
run_outside() { # $1=function name -> "rc:sawmessage"
|
||||
local fn="$1" out rc
|
||||
out=$( cd "$TMP" && bash -c "set -e; source '$HERE/detect-platform.sh'; $fn" 2>&1 ); rc=$?
|
||||
printf '%s:%s' "$rc" "$(grep -qi 'not a git repository' <<<"$out" && echo yes || echo no)"
|
||||
}
|
||||
check() { if [ "$2" = "$3" ]; then echo " PASS $1 ($2)"; else echo " FAIL $1: got $2, want $3"; fail=1; fi; }
|
||||
|
||||
# $TMP must not be inside a git repo. Do not SKIP on failure: be-coder-07 showed the
|
||||
# original SKIP exited 0, so pointing TMPDIR beneath a git worktree made this test PASS
|
||||
# against unchanged main. A skip that exits 0 is indistinguishable from a pass.
|
||||
# GIT_CEILING_DIRECTORIES stops git walking above $TMP, making the condition hold
|
||||
# regardless of where TMPDIR lives, rather than merely detecting when it does not.
|
||||
# GIT_CEILING_DIRECTORIES is matched against the PHYSICAL path -- a symlinked TMPDIR
|
||||
# (/tmp is commonly one) makes the logical path never match, and the ceiling silently
|
||||
# does nothing. Resolve it before exporting.
|
||||
TMP="$(cd "$TMP" && pwd -P)"
|
||||
export GIT_CEILING_DIRECTORIES="$TMP"
|
||||
if ( cd "$TMP" && git rev-parse --git-dir >/dev/null 2>&1 ); then
|
||||
echo " FAIL scratch dir is inside a git repo even with GIT_CEILING_DIRECTORIES set;"
|
||||
echo " the outside-a-repo precondition cannot be established -- refusing to report a result"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "== outside a git repo: rc=1 AND the diagnostic is emitted =="
|
||||
check "detect_platform" "$(run_outside detect_platform)" "1:yes"
|
||||
check "get_repo_info" "$(run_outside get_repo_info)" "1:yes"
|
||||
|
||||
echo "== inside a git repo the functions still work =="
|
||||
git init -q "$TMP/repo" 2>/dev/null
|
||||
git -C "$TMP/repo" remote add origin https://git.mosaicstack.dev/mosaicstack/stack.git 2>/dev/null
|
||||
out=$( cd "$TMP/repo" && bash -c "set -e; source '$HERE/detect-platform.sh'; detect_platform" 2>&1 ); rc=$?
|
||||
if [ "$rc" -eq 0 ] && grep -qi 'gitea' <<<"$out"; then echo " PASS detect_platform in-repo (rc=0, $out)"
|
||||
else echo " FAIL detect_platform in-repo: rc=$rc out=$out"; fail=1; fi
|
||||
|
||||
[ "$fail" -eq 0 ] && echo "OK detect-platform fails loudly outside a repo" || echo "FAILED"
|
||||
exit "$fail"
|
||||
@@ -1,64 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Regression: the tea-failure diagnostic must be STATUS-NEUTRAL.
|
||||
#
|
||||
# Found by be-coder-08 reviewing PR #1086. At all three call sites the diagnostic is emitted
|
||||
# immediately BEFORE the Gitea API fallback. Written as the last command of an && list:
|
||||
# declare -F explain_... >/dev/null && explain_...
|
||||
# under `set -e` a FAILING diagnostic exits and the fallback never runs -- a diagnostic that
|
||||
# suppresses the recovery path it exists to explain. It misbehaves ONLY when the helper is
|
||||
# PRESENT, so the helper-absent path (pre-#1086 behaviour) keeps working and reads as a
|
||||
# passing control.
|
||||
#
|
||||
# TWO DEFECTS IN THE FIRST VERSION OF THIS TEST, both found by be-coder-08:
|
||||
# 1. `out=$( ... ) 2>"$errto"` applies the redirection to the ASSIGNMENT, not to the
|
||||
# command substitution, so the probe's stderr was never actually pointed at /dev/full
|
||||
# and the /dev/full rows proved nothing. Verified: `out=$(echo x >&2) 2>/dev/full`
|
||||
# leaks to the terminal and returns 0; the redirect must be INSIDE the substitution.
|
||||
# 2. `eval "$CONSTRUCT"` changes `set -e` semantics for a bare && list, so the probe did
|
||||
# not exercise the construct as the shipped file executes it. It now writes the line
|
||||
# into a real script and runs it -- same parse, same set -e rules, no eval.
|
||||
# The construct is still LIFTED FROM THE SHIPPED FILE: retyping the fixed form makes the
|
||||
# probe pass on a build whose real call sites still carry the bare && form.
|
||||
set -uo pipefail
|
||||
fail=0
|
||||
GIT_DIR_UNDER_TEST="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
probe() { # $1=present|absent $2=stderr target $3=source file -> "rc:fallback"
|
||||
local helper="$1" errto="$2" src="$3" construct script out rc
|
||||
construct=$(grep -m1 'explain_tea_user_does_not_exist' "$GIT_DIR_UNDER_TEST/$src" | sed 's/^[[:space:]]*//')
|
||||
[ -n "$construct" ] || { printf 'no-construct:no'; return; }
|
||||
script="$TMP/probe.sh"
|
||||
{
|
||||
echo '#!/bin/bash'
|
||||
echo 'set -e'
|
||||
echo 'explain_tea_user_does_not_exist() { echo "diagnostic" >&2; }'
|
||||
[ "$helper" = absent ] && echo 'unset -f explain_tea_user_does_not_exist'
|
||||
echo "$construct" # the shipped line, parsed by a real shell
|
||||
echo 'echo FALLBACK_REACHED'
|
||||
} > "$script"
|
||||
# redirect INSIDE the substitution so the subshell's stderr really is $errto
|
||||
out=$( bash "$script" 2>"$errto" ); rc=$?
|
||||
printf '%s:%s' "$rc" "$(grep -q FALLBACK_REACHED <<<"$out" && echo yes || echo no)"
|
||||
}
|
||||
|
||||
check() { if [ "$2" = "$3" ]; then echo " PASS $1 ($2)"; else echo " FAIL $1: got $2, want $3"; fail=1; fi; }
|
||||
|
||||
echo "== diagnostic must not alter exit status or skip the fallback =="
|
||||
# /dev/full makes every stderr write fail -- the real-world shape is a closed or full fd.
|
||||
for src in pr-create.sh issue-view.sh issue-create.sh; do
|
||||
check "$src stderr OK / helper present" "$(probe present /dev/null "$src")" "0:yes"
|
||||
check "$src stderr OK / helper absent " "$(probe absent /dev/null "$src")" "0:yes"
|
||||
check "$src stderr FAILING / helper present" "$(probe present /dev/full "$src")" "0:yes"
|
||||
check "$src stderr FAILING / helper absent " "$(probe absent /dev/full "$src")" "0:yes"
|
||||
done
|
||||
|
||||
echo "== all three call sites use the status-neutral form =="
|
||||
for f in pr-create.sh issue-view.sh issue-create.sh; do
|
||||
p="$GIT_DIR_UNDER_TEST/$f"
|
||||
grep -q '{ declare -F explain_tea_user_does_not_exist >/dev/null && explain_tea_user_does_not_exist; } || true' "$p" \
|
||||
&& echo " PASS $f guarded" || { echo " FAIL $f: diagnostic is not status-neutral"; fail=1; }
|
||||
done
|
||||
|
||||
[ "$fail" -eq 0 ] && echo "OK diagnostic is status-neutral" || echo "FAILED"
|
||||
exit "$fail"
|
||||
@@ -1,150 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Regression: issue-close.sh must NOT close an issue when the closing comment could not
|
||||
# be posted, and comment+close must be made by ONE principal.
|
||||
#
|
||||
# Guards two defects fixed together (see #1081):
|
||||
# 1. `tea issue comment` is not a subcommand -- tea exposes comments as the TOP-LEVEL
|
||||
# `tea comment`. The old call always failed, was unchecked, and the issue closed
|
||||
# anyway, losing the record of WHY it was closed.
|
||||
# 2. Routing the comment through the token-authenticated API helper while the close
|
||||
# used --login would attribute one operation to two principals.
|
||||
#
|
||||
# SAFETY (rev-974, #1085 review 130): this test previously ran under `set -uo pipefail`
|
||||
# with unchecked mkdir/redirect/cd, then prepended a possibly-nonexistent $MOCK_BIN to
|
||||
# PATH -- while `git remote add origin` names the REAL repository. Forcing setup failure
|
||||
# with an unwritable AGENT_WORK_ROOT made it `git init` in its CALLER's directory and
|
||||
# invoke the real, provider-mutating issue-close.sh. Setup now fails closed, and both
|
||||
# `tea` and `curl` are asserted to resolve INSIDE $MOCK_BIN before any target run.
|
||||
set -euo pipefail
|
||||
# NOTE: with `set -e`, `grep -q X && fail "..."` is a trap -- the ABSENT case (grep rc=1,
|
||||
# which is the PASSING case for a must-not-appear assertion) is the last command of an &&
|
||||
# list and silently terminates the script with no message. Every must-not-appear check
|
||||
# below is therefore an if-block. This is the same set -e + &&-list defect be-coder-08
|
||||
# found in #1086, reintroduced here by adding `set -e` for the sandbox-safety fix.
|
||||
|
||||
WORK_ROOT="${AGENT_WORK_ROOT:-${TMPDIR:-/tmp}}"
|
||||
SANDBOX="$WORK_ROOT/issue-close-fail-closed-test-$$"
|
||||
MOCK_BIN="$SANDBOX/bin"; REPO_DIR="$SANDBOX/repo"; CALLS="$SANDBOX/calls.log"
|
||||
cleanup() { rm -rf "$SANDBOX"; }
|
||||
trap cleanup EXIT
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TARGET="$SCRIPT_DIR/issue-close.sh"
|
||||
[ -f "$TARGET" ] || { echo "FAIL: issue-close.sh not found beside this test"; exit 1; }
|
||||
fail() { echo "FAIL: $*"; exit 1; }
|
||||
|
||||
# Every setup step is checked. Under `set -e` these abort; the explicit || fail keeps the
|
||||
# reason legible instead of a bare non-zero exit.
|
||||
mkdir -p "$MOCK_BIN" "$REPO_DIR" || fail "setup: cannot create sandbox under $WORK_ROOT"
|
||||
: > "$CALLS" || fail "setup: cannot write calls log at $CALLS"
|
||||
cd "$REPO_DIR" || fail "setup: cannot cd into $REPO_DIR"
|
||||
git init -q || fail "setup: git init failed"
|
||||
git remote add origin https://git.mosaicstack.dev/mosaicstack/stack.git || fail "setup: git remote add failed"
|
||||
export PATH="$MOCK_BIN:$PATH" CALLS
|
||||
export GITEA_URL="https://git.mosaicstack.dev"
|
||||
export GITEA_TOKEN="redacted-test-token"
|
||||
|
||||
cat > "$MOCK_BIN/curl" <<'EOF'
|
||||
#!/bin/bash
|
||||
method=GET; url=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-X) method="$2"; shift 2 ;;
|
||||
http*|https*) url="$1"; shift ;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
printf 'curl %s %s\n' "$method" "$url" >> "$CALLS"
|
||||
[ "${MOCK_CURL_FAIL:-}" = "1" ] && [ "$method" = "POST" ] && exit 22
|
||||
exit 0
|
||||
EOF
|
||||
chmod +x "$MOCK_BIN/curl"
|
||||
|
||||
mk_tea() { # $1 = exit code for a comment attempt; $2 = login list (empty => no login)
|
||||
local rc="$1" login="${2-}"
|
||||
cat > "$MOCK_BIN/tea" <<EOF
|
||||
#!/bin/bash
|
||||
printf 'tea %s\n' "\$*" >> "$CALLS"
|
||||
if [[ "\$*" == *"login list"* ]]; then
|
||||
printf '%s\n' '${login}'; exit 0
|
||||
fi
|
||||
# Fail ANY comment attempt -- both the correct top-level \`tea comment\` and the broken
|
||||
# \`tea issue comment\` -- so an unfixed script exercises the DEFECT rather than tripping
|
||||
# a setup assertion.
|
||||
if [[ "\$1" == "comment" || ( "\$1" == "issue" && "\$2" == "comment" ) ]]; then exit $rc; fi
|
||||
exit 0
|
||||
EOF
|
||||
chmod +x "$MOCK_BIN/tea"
|
||||
}
|
||||
LOGIN_JSON='[{"name":"git.mosaicstack.dev","url":"https://git.mosaicstack.dev"}]'
|
||||
|
||||
# The mocks must be the ones that run. Without this, a failed setup silently falls through
|
||||
# to the real tea/curl and the "test" mutates the real provider.
|
||||
assert_mocked() {
|
||||
local w
|
||||
for w in tea curl; do
|
||||
p=$(command -v "$w" || true)
|
||||
[ -n "$p" ] || fail "SAFETY: $w does not resolve at all"
|
||||
case "$p" in
|
||||
"$MOCK_BIN"/*) : ;;
|
||||
*) fail "SAFETY: $w resolves to $p, OUTSIDE the sandbox -- refusing to invoke the target" ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
run_target() { # never let a target failure abort the test; we assert on rc
|
||||
# Call sites MUST use `rc=0; run_target ... || rc=$?` -- a bare `run_target ...; rc=$?`
|
||||
# lets the non-zero RETURN trip set -e in the CALLER before rc is ever read.
|
||||
set +e; bash "$TARGET" "$@" >/dev/null 2>&1; local rc=$?; set -e; return $rc
|
||||
}
|
||||
|
||||
# ── tea path ────────────────────────────────────────────────────────────────────────
|
||||
# 1. NEGATIVE (the regression): comment fails => must NOT close, must exit non-zero
|
||||
mk_tea 1 "$LOGIN_JSON"; : > "$CALLS"; assert_mocked
|
||||
rc=0; run_target -i 42 -c "closing note" || rc=$?
|
||||
grep -qE 'tea (issue )?comment' "$CALLS" || fail "no comment attempt -- setup did not reach the tea branch"
|
||||
if grep -q 'tea issue close' "$CALLS"; then fail "ISSUE CLOSED AFTER THE COMMENT FAILED -- the regression"; fi
|
||||
[ "$rc" -ne 0 ] || fail "comment failed but issue-close exited 0 -- FAIL-OPEN"
|
||||
|
||||
# 2. POSITIVE: comment succeeds => close proceeds, exit 0
|
||||
mk_tea 0 "$LOGIN_JSON"; : > "$CALLS"; assert_mocked
|
||||
rc=0; run_target -i 42 -c "closing note" || rc=$?
|
||||
[ "$rc" -eq 0 ] || fail "comment succeeded but issue-close exited $rc"
|
||||
grep -q 'tea issue close' "$CALLS" || fail "issue not closed even though the comment succeeded"
|
||||
|
||||
# 3. must use top-level `tea comment`, never `tea issue comment`
|
||||
if grep -q 'tea issue comment' "$CALLS"; then fail "used 'tea issue comment' -- not a valid subcommand"; fi
|
||||
|
||||
# 4. ONE PRINCIPAL: comment and close must carry the SAME --login
|
||||
c=$(grep -m1 '^tea comment' "$CALLS" | grep -o -- '--login [^ ]*' | awk '{print $2}')
|
||||
k=$(grep -m1 '^tea issue close' "$CALLS" | grep -o -- '--login [^ ]*' | awk '{print $2}')
|
||||
[ -n "$c" ] || fail "comment carried no --login"
|
||||
[ "$c" = "$k" ] || fail "MIXED PRINCIPALS: comment=$c close=$k"
|
||||
|
||||
# ── no-login / API fallback path ────────────────────────────────────────────────────
|
||||
# rev-974: the delta also adds fail-closed behaviour to this branch, and the suite never
|
||||
# reached it -- replacing the whole fallback contract with an unconditional close still
|
||||
# passed. These assert the POSTCONDITION (which HTTP calls happened, in what order),
|
||||
# not merely that a command ran.
|
||||
# 5. no login + comment FAILS => POST attempted, NO PATCH, non-zero
|
||||
mk_tea 0 ""; : > "$CALLS"; assert_mocked
|
||||
rc=0; MOCK_CURL_FAIL=1 run_target -i 42 -c "closing note" || rc=$?
|
||||
grep -q 'curl POST' "$CALLS" || fail "API path: no comment POST attempted"
|
||||
if grep -q 'curl PATCH' "$CALLS"; then fail "API path: ISSUE CLOSED (PATCH) AFTER THE COMMENT POST FAILED"; fi
|
||||
[ "$rc" -ne 0 ] || fail "API path: comment failed but exited 0 -- FAIL-OPEN"
|
||||
|
||||
# 6. no login + comment SUCCEEDS => POST strictly BEFORE PATCH, exit 0
|
||||
mk_tea 0 ""; : > "$CALLS"; assert_mocked
|
||||
rc=0; run_target -i 42 -c "closing note" || rc=$?
|
||||
[ "$rc" -eq 0 ] || fail "API path: comment succeeded but exited $rc"
|
||||
order=$(grep -oE 'curl (POST|PATCH)' "$CALLS" | awk '{print $2}' | paste -sd, -)
|
||||
[ "$order" = "POST,PATCH" ] || fail "API path: expected POST,PATCH -- got '${order:-<none>}'"
|
||||
|
||||
# 7. no login + NO comment => PATCH only, never a POST
|
||||
mk_tea 0 ""; : > "$CALLS"; assert_mocked
|
||||
rc=0; run_target -i 42 || rc=$?
|
||||
[ "$rc" -eq 0 ] || fail "API path: no-comment close exited $rc"
|
||||
if grep -q 'curl POST' "$CALLS"; then fail "API path: posted a comment when none was requested"; fi
|
||||
grep -q 'curl PATCH' "$CALLS" || fail "API path: issue not closed when no comment was requested"
|
||||
|
||||
echo "issue-close.sh fail-closed + single-principal regression passed"
|
||||
@@ -41,7 +41,7 @@ get_gitea_basic_auth() {
|
||||
printf 'resolved\n' >> "${MOSAIC_TEST_BASIC_RESOLUTION_LOG:?}"
|
||||
if [[ "${MOSAIC_TEST_BASIC_AVAILABLE:-false}" == "true" ]]; then
|
||||
printf 'fixture-user:fixture-password\n'
|
||||
return "${MOSAIC_TEST_BASIC_RC:-0}"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
@@ -99,9 +99,6 @@ out_file=""
|
||||
data=""
|
||||
config=""
|
||||
auth_mode="none"
|
||||
has_max_filesize=0
|
||||
has_max_time=0
|
||||
has_connect_timeout=0
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-o)
|
||||
@@ -128,18 +125,6 @@ while [[ $# -gt 0 ]]; do
|
||||
fi
|
||||
shift 2
|
||||
;;
|
||||
--max-filesize)
|
||||
has_max_filesize=1
|
||||
shift 2
|
||||
;;
|
||||
--max-time)
|
||||
has_max_time=1
|
||||
shift 2
|
||||
;;
|
||||
--connect-timeout)
|
||||
has_connect_timeout=1
|
||||
shift 2
|
||||
;;
|
||||
-H|--header|-u|--user)
|
||||
if [[ "$2" == *"fixture-token"* ]]; then
|
||||
: > "${MOSAIC_TEST_TOKEN_ARGV_MARKER:?}"
|
||||
@@ -167,7 +152,6 @@ elif [[ "$config" == *"user = \"fixture-user:fixture-password\""* ]]; then
|
||||
: > "${MOSAIC_TEST_BASIC_CONFIG_MARKER:?}"
|
||||
fi
|
||||
printf '%s %s %s\n' "$method" "$auth_mode" "$url" >> "${MOSAIC_TEST_CURL_LOG:?}"
|
||||
printf '%s:%s:%s\n' "$has_max_filesize" "$has_max_time" "$has_connect_timeout" >> "${MOSAIC_TEST_CURL_BOUNDS_LOG:?}"
|
||||
|
||||
case "$url" in
|
||||
*/pulls/42)
|
||||
@@ -190,11 +174,7 @@ case "$url" in
|
||||
*/pulls/42/commits*)
|
||||
case "${MOSAIC_TEST_COMMITS_MODE:?}" in
|
||||
verified)
|
||||
if [[ "${MOSAIC_TEST_EMAIL_MODE:-safe}" == "escape" ]]; then
|
||||
body='[{"sha":"2222222222222222222222222222222222222222","commit":{"author":{"name":"Alice","email":"alice+\u001b[[email protected]"}},"author":{"login":"alice"}},{"sha":"1111111111111111111111111111111111111111","commit":{"author":{"name":"Poster","email":"[email protected]"}},"author":{"login":"poster"}}]'
|
||||
else
|
||||
body='[{"sha":"2222222222222222222222222222222222222222","commit":{"author":{"name":"Alice","email":"[email protected]"}},"author":{"login":"alice"}},{"sha":"1111111111111111111111111111111111111111","commit":{"author":{"name":"Poster","email":"[email protected]"}},"author":{"login":"poster"}}]'
|
||||
fi
|
||||
body='[{"sha":"2222222222222222222222222222222222222222","commit":{"author":{"name":"Alice","email":"[email protected]"}},"author":{"login":"alice"}},{"sha":"1111111111111111111111111111111111111111","commit":{"author":{"name":"Poster","email":"[email protected]"}},"author":{"login":"poster"}}]'
|
||||
;;
|
||||
null-login)
|
||||
body='[{"sha":"1111111111111111111111111111111111111111","commit":{"author":{"name":"Poster","email":"[email protected]"}},"author":{"login":"poster"}},{"sha":"3333333333333333333333333333333333333333","commit":{"author":{"name":"Unresolved Author","email":"[email protected]\n\u001b[31m"}},"author":null}]'
|
||||
@@ -248,10 +228,6 @@ else
|
||||
printf '%s' "$body"
|
||||
fi
|
||||
printf '%s' "$code"
|
||||
case "${MOSAIC_TEST_CURL_FAILURE:-none}" in
|
||||
oversize) exit 63 ;;
|
||||
stalled) exit 28 ;;
|
||||
esac
|
||||
SH
|
||||
|
||||
chmod +x "$case_dir/detect-platform.sh" "$case_dir/pr-metadata.sh" \
|
||||
@@ -264,7 +240,6 @@ run_case() {
|
||||
shift 2
|
||||
MOSAIC_TEST_COMMITS_MODE="$mode" \
|
||||
MOSAIC_TEST_CURL_LOG="$case_dir/curl.log" \
|
||||
MOSAIC_TEST_CURL_BOUNDS_LOG="$case_dir/curl-bounds.log" \
|
||||
MOSAIC_TEST_MERGE_PAYLOAD="$case_dir/merge-payload.json" \
|
||||
MOSAIC_TEST_TOKEN_ARGV_MARKER="$case_dir/token-in-argv" \
|
||||
MOSAIC_TEST_BASIC_ARGV_MARKER="$case_dir/basic-in-argv" \
|
||||
@@ -310,30 +285,6 @@ fi
|
||||
if grep -q '/users/' "$verified_dir/curl.log" 2>/dev/null; then
|
||||
fail "verified path performed a forbidden second /users lookup"
|
||||
fi
|
||||
if grep -qv '^1:1:1$' "$verified_dir/curl-bounds.log"; then
|
||||
fail "verified path did not apply size/max-time/connect-time bounds to every provider download"
|
||||
fi
|
||||
|
||||
# A linked email containing a terminal escape must block before mutation.
|
||||
escape_email_dir=$(make_case escape-email)
|
||||
set +e
|
||||
escape_email_output=$(MOSAIC_TEST_EMAIL_MODE=escape run_case "$escape_email_dir" verified --co-author-trailers --escalate-to tl-mosaic 2>&1)
|
||||
escape_email_rc=$?
|
||||
set -e
|
||||
[[ "$escape_email_rc" -ne 0 ]] || fail "control-byte email unexpectedly passed"
|
||||
[[ "$escape_email_output" == *"unusable linked identity"* ]] || fail "control-byte email refusal lost its diagnostic"
|
||||
[[ ! -e "$escape_email_dir/merge-payload.json" ]] || fail "control-byte email reached the merge API"
|
||||
|
||||
# Curl transfer and duration failures must remain failures even with HTTP 200.
|
||||
for failure_mode in oversize stalled; do
|
||||
failure_dir=$(make_case "curl-$failure_mode")
|
||||
set +e
|
||||
failure_output=$(MOSAIC_TEST_CURL_FAILURE="$failure_mode" run_case "$failure_dir" verified --co-author-trailers --escalate-to tl-mosaic 2>&1)
|
||||
failure_rc=$?
|
||||
set -e
|
||||
[[ "$failure_rc" -ne 0 ]] || fail "curl $failure_mode failure was discarded: $failure_output"
|
||||
[[ ! -e "$failure_dir/merge-payload.json" ]] || fail "curl $failure_mode failure reached the merge API"
|
||||
done
|
||||
|
||||
# The authenticated head is re-read under the mutation credential but cannot
|
||||
# replace the canonical preflight/review head. A move blocks before enumeration
|
||||
@@ -364,44 +315,39 @@ set -e
|
||||
[[ ! -e "$token_missing_dir/basic-resolution.log" ]] || fail "missing token resolved Basic Auth after identity failure"
|
||||
[[ ! -e "$token_missing_dir/curl.log" ]] || fail "missing token reached a provider request"
|
||||
|
||||
# A failed Basic resolver must never use its nonempty output or reach mutation.
|
||||
basic_rc_dir=$(make_case basic-resolver-rc)
|
||||
set +e
|
||||
basic_rc_output=$(MOSAIC_TEST_BASIC_AVAILABLE=true MOSAIC_TEST_BASIC_RC=91 MOSAIC_TEST_FALLBACK_MODE=inspection \
|
||||
run_case "$basic_rc_dir" verified --co-author-trailers --escalate-to tl-mosaic 2>&1)
|
||||
basic_rc_rc=$?
|
||||
set -e
|
||||
[[ "$basic_rc_rc" -ne 0 ]] || fail "failed Basic resolver output unexpectedly authorized a merge: $basic_rc_output"
|
||||
[[ ! -e "$basic_rc_dir/merge-payload.json" ]] || fail "failed Basic resolver reached the merge API"
|
||||
|
||||
# HTTP 401 never changes principals: inspection rejection fails closed without
|
||||
# resolving or attempting Basic Auth.
|
||||
# Token rejection during inspection must fall back to Basic Auth, then repeat
|
||||
# BOTH inspection and merge with that one Basic credential handle.
|
||||
fallback_inspect_dir=$(make_case fallback-inspection)
|
||||
set +e
|
||||
fallback_inspect_output=$(MOSAIC_TEST_BASIC_AVAILABLE=true MOSAIC_TEST_FALLBACK_MODE=inspection \
|
||||
run_case "$fallback_inspect_dir" verified --co-author-trailers --escalate-to tl-mosaic 2>&1)
|
||||
fallback_inspect_rc=$?
|
||||
set -e
|
||||
[[ "$fallback_inspect_rc" -ne 0 ]] || fail "inspection token rejection unexpectedly changed principals"
|
||||
[[ "$fallback_inspect_output" == *"refusing cross-principal credential fallback"* ]] || fail "inspection token rejection lost its refusal diagnostic"
|
||||
[[ ! -e "$fallback_inspect_dir/basic-resolution.log" ]] || fail "inspection token rejection resolved Basic Auth"
|
||||
[[ ! -e "$fallback_inspect_dir/merge-payload.json" ]] || fail "inspection token rejection reached merge mutation"
|
||||
[[ "$fallback_inspect_rc" -eq 0 ]] || fail "inspection fallback expected rc=0, got rc=$fallback_inspect_rc: $fallback_inspect_output"
|
||||
[[ -s "$fallback_inspect_dir/merge-payload.json" ]] || fail "inspection fallback did not reach the merge API"
|
||||
[[ -e "$fallback_inspect_dir/basic-via-config" ]] || fail "inspection fallback did not transport Basic Auth through stdin config"
|
||||
[[ ! -e "$fallback_inspect_dir/basic-in-argv" ]] || fail "inspection fallback exposed Basic Auth in curl argv"
|
||||
[[ ! -e "$fallback_inspect_dir/metadata-in-argv" ]] || fail "inspection fallback exposed PR metadata in child argv"
|
||||
[[ "$(wc -l < "$fallback_inspect_dir/token-resolution.log")" -eq 1 ]] || fail "inspection fallback did not resolve token exactly once"
|
||||
[[ "$(wc -l < "$fallback_inspect_dir/basic-resolution.log")" -eq 1 ]] || fail "inspection fallback did not resolve Basic Auth exactly once"
|
||||
inspect_sequence=$(awk '{print $1 ":" $2}' "$fallback_inspect_dir/curl.log" | paste -sd, -)
|
||||
[[ "$inspect_sequence" == "GET:token" ]] || fail "inspection rejection made unexpected provider calls (calls=$inspect_sequence)"
|
||||
[[ "$inspect_sequence" == "GET:token,GET:basic,GET:basic,POST:basic" ]] || fail "inspection fallback was not bound per credential (calls=$inspect_sequence)"
|
||||
|
||||
# Token rejection at merge likewise fails closed without cross-principal retry.
|
||||
# Token rejection at merge is a separate seam: Basic fallback must re-inspect
|
||||
# instead of reusing evidence gathered under the rejected token.
|
||||
fallback_merge_dir=$(make_case fallback-merge)
|
||||
set +e
|
||||
fallback_merge_output=$(MOSAIC_TEST_BASIC_AVAILABLE=true MOSAIC_TEST_FALLBACK_MODE=merge \
|
||||
run_case "$fallback_merge_dir" verified --co-author-trailers --escalate-to tl-mosaic 2>&1)
|
||||
fallback_merge_rc=$?
|
||||
set -e
|
||||
[[ "$fallback_merge_rc" -ne 0 ]] || fail "merge token rejection unexpectedly changed principals"
|
||||
[[ "$fallback_merge_output" == *"refusing cross-principal credential fallback"* ]] || fail "merge token rejection lost its refusal diagnostic"
|
||||
[[ ! -e "$fallback_merge_dir/basic-resolution.log" ]] || fail "merge token rejection resolved Basic Auth"
|
||||
[[ ! -e "$fallback_merge_dir/merge-payload.json" ]] || fail "merge token rejection recorded a successful payload"
|
||||
[[ "$fallback_merge_rc" -eq 0 ]] || fail "merge fallback expected rc=0, got rc=$fallback_merge_rc: $fallback_merge_output"
|
||||
[[ -s "$fallback_merge_dir/merge-payload.json" ]] || fail "merge fallback did not reach a successful merge API payload"
|
||||
[[ -e "$fallback_merge_dir/basic-via-config" ]] || fail "merge fallback did not transport Basic Auth through stdin config"
|
||||
[[ ! -e "$fallback_merge_dir/basic-in-argv" ]] || fail "merge fallback exposed Basic Auth in curl argv"
|
||||
[[ ! -e "$fallback_merge_dir/metadata-in-argv" ]] || fail "merge fallback exposed PR metadata in child argv"
|
||||
merge_sequence=$(awk '{print $1 ":" $2}' "$fallback_merge_dir/curl.log" | paste -sd, -)
|
||||
[[ "$merge_sequence" == "GET:token,GET:token,POST:token" ]] || fail "merge rejection made unexpected provider calls (calls=$merge_sequence)"
|
||||
[[ "$merge_sequence" == "GET:token,GET:token,POST:token,GET:basic,GET:basic,POST:basic" ]] || fail "merge fallback reused cross-credential evidence (calls=$merge_sequence)"
|
||||
|
||||
# BLOCK path: a commit email exists but author.login is null. It must name both
|
||||
# facts, name the escalation principal, and never reach the merge endpoint.
|
||||
@@ -459,7 +405,6 @@ set -e
|
||||
[[ "$error_output" == *"branch policy rejected"* ]] || fail "provider error response was discarded"
|
||||
[[ "$error_output" == *'\n\x1b[31m'* ]] || fail "provider error response did not escape control characters"
|
||||
[[ "$error_output" != *$'\033'* ]] || fail "provider error response emitted a raw terminal escape"
|
||||
[[ "$error_output" != *"Basic Auth fallback"* ]] || fail "provider error advertised removed Basic Auth fallback"
|
||||
[[ ! -e "$error_dir/basic-resolution.log" ]] || fail "HTTP 409 policy denial incorrectly triggered Basic Auth fallback"
|
||||
|
||||
# Authorization denials likewise fail closed instead of changing principals.
|
||||
@@ -471,7 +416,6 @@ forbidden_rc=$?
|
||||
set -e
|
||||
[[ "$forbidden_rc" -ne 0 ]] || fail "HTTP 403 authorization denial unexpectedly passed"
|
||||
[[ "$forbidden_output" == *"HTTP 403"* ]] || fail "authorization denial omitted the HTTP status"
|
||||
[[ "$forbidden_output" != *"Basic Auth fallback"* ]] || fail "authorization denial advertised removed Basic Auth fallback"
|
||||
[[ ! -e "$forbidden_dir/basic-resolution.log" ]] || fail "HTTP 403 authorization denial incorrectly triggered Basic Auth fallback"
|
||||
|
||||
# The BLOCK destination cannot be generic or inferred after failure: opting in
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"lint": "eslint src",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "vitest run --passWithNoTests && pnpm run test:framework-shell",
|
||||
"test:framework-shell": "bash framework/tools/quality/scripts/check-test-enumeration.sh && bash framework/tools/quality/scripts/test-check-test-enumeration.sh && python3 src/lease-broker/daemon_deadline_unittest.py && python3 src/lease-broker/normative_fragments_unittest.py && python3 src/lease-broker/receipt_challenge_unittest.py && python3 src/lease-broker/context_recovery_unittest.py && python3 src/lease-broker/recovery_runtime_unittest.py && python3 src/lease-broker/recovery_b1_adversarial_unittest.py && python3 src/lease-broker/framework_skill_portability_unittest.py && python3 src/mutator-gate/runtime_tools_unittest.py && python3 src/mutator-gate/runtime_launch_guard_unittest.py && python3 src/mutator-gate/version_coupling_unittest.py && python3 framework/tools/lease-broker/check-runtime-launches.py --root ../.. && bash framework/tools/codex/test-pr-diff-context.sh && bash framework/tools/qa/test-deps-preflight.sh && bash framework/tools/git/test-pr-review-gitea-comment.sh && bash framework/tools/git/test-pr-review-repo-host-override.sh && bash framework/tools/git/test-ci-queue-wait-branch-absent.sh && bash framework/tools/git/test-ci-queue-wait-tristate.sh && bash framework/tools/git/test-ci-queue-wait-github-checks.sh && bash framework/tools/git/test-pr-merge-queue-branch.sh && bash framework/tools/git/test-pr-merge-head-pin.sh && bash framework/tools/git/test-pr-merge-message-field.sh && bash framework/tools/git/test-git-credential-mosaic.sh && bash framework/tools/git/test-gitea-token-identity.sh && bash framework/tools/git/test-explain-diagnostic-status-neutral.sh && bash framework/tools/git/test-detect-platform-outside-repo.sh && bash framework/tools/woodpecker/test-terminal-green-contract.sh && bash framework/tools/_scripts/test-install-ordering-guard.sh && bash framework/tools/tmux/agent-send.test.sh && bash framework/tools/wake/test-wake-store-ack.sh && bash framework/tools/wake/test-wake-store-enqueue-race.sh && bash framework/tools/wake/test-wake-digest-hmac.sh && bash framework/tools/wake/test-wake-digest-quarantine.sh && bash framework/tools/wake/test-wake-detector.sh && bash framework/tools/wake/test-wake-fn-oracle.sh && bash framework/tools/wake/test-wake-reconcile.sh && bash framework/tools/wake/test-wake-beacon.sh && bash framework/tools/wake/test-wake-preimage.sh && bash framework/tools/wake/test-wake-install.sh"
|
||||
"test:framework-shell": "bash framework/tools/quality/scripts/check-test-enumeration.sh && bash framework/tools/quality/scripts/test-check-test-enumeration.sh && python3 src/lease-broker/daemon_deadline_unittest.py && python3 src/lease-broker/normative_fragments_unittest.py && python3 src/lease-broker/receipt_challenge_unittest.py && python3 src/lease-broker/context_recovery_unittest.py && python3 src/lease-broker/recovery_runtime_unittest.py && python3 src/lease-broker/recovery_b1_adversarial_unittest.py && python3 src/lease-broker/framework_skill_portability_unittest.py && python3 src/mutator-gate/runtime_tools_unittest.py && python3 src/mutator-gate/runtime_launch_guard_unittest.py && python3 src/mutator-gate/version_coupling_unittest.py && python3 framework/tools/lease-broker/check-runtime-launches.py --root ../.. && bash framework/tools/codex/test-pr-diff-context.sh && bash framework/tools/qa/test-deps-preflight.sh && bash framework/tools/git/test-pr-review-gitea-comment.sh && bash framework/tools/git/test-pr-review-repo-host-override.sh && bash framework/tools/git/test-ci-queue-wait-branch-absent.sh && bash framework/tools/git/test-ci-queue-wait-tristate.sh && bash framework/tools/git/test-ci-queue-wait-github-checks.sh && bash framework/tools/git/test-pr-merge-queue-branch.sh && bash framework/tools/git/test-pr-merge-head-pin.sh && bash framework/tools/git/test-pr-merge-message-field.sh && bash framework/tools/git/test-git-credential-mosaic.sh && bash framework/tools/git/test-gitea-token-identity.sh && bash framework/tools/woodpecker/test-terminal-green-contract.sh && bash framework/tools/_scripts/test-install-ordering-guard.sh && bash framework/tools/tmux/agent-send.test.sh && bash framework/tools/wake/test-wake-store-ack.sh && bash framework/tools/wake/test-wake-store-enqueue-race.sh && bash framework/tools/wake/test-wake-digest-hmac.sh && bash framework/tools/wake/test-wake-digest-quarantine.sh && bash framework/tools/wake/test-wake-detector.sh && bash framework/tools/wake/test-wake-fn-oracle.sh && bash framework/tools/wake/test-wake-reconcile.sh && bash framework/tools/wake/test-wake-beacon.sh && bash framework/tools/wake/test-wake-preimage.sh && bash framework/tools/wake/test-wake-install.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mosaicstack/brain": "workspace:*",
|
||||
|
||||
Reference in New Issue
Block a user