fix(git-tools): admin-gated --no-ci-expected merge assertion for CI-less repositories (#1373)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
Co-authored-by: code-be-01 <[email protected]>
This commit was merged in pull request #1373.
This commit is contained in:
@@ -111,7 +111,7 @@ approve path carries the trap.) `pr-review.sh` sends the correct token for the d
|
|||||||
Whatever you use, re-read `GET /pulls/{n}/reviews` and assert the state before reporting a verdict
|
Whatever you use, re-read `GET /pulls/{n}/reviews` and assert the state before reporting a verdict
|
||||||
placed.
|
placed.
|
||||||
|
|
||||||
The guard exits nonzero for any provider-asserted non-green, missing, or malformed CI state. If credentials or the provider are unavailable, it emits `CANNOT_ASSERT` and writes a JSONL audit record. Push degrades to exit 0 so recovery work is not bricked; merge holds with retryable exit 75 until the provider recovers, then self-clears without manual reset. Neither outcome is evidence that CI was clear. `pr-merge.sh` automatically inspects the exact PR head repository and full commit SHA rather than its `main` base; this also handles fork PRs without branch-name ambiguity. Pass `--expect-head <approved-full-sha>` to bind a commit-specific review or merge-gate verdict; Gitea uses atomic `head_commit_id` and GitHub uses `--match-head-commit`.
|
The guard exits nonzero for any provider-asserted non-green, missing, or malformed CI state. If credentials or the provider are unavailable, it emits `CANNOT_ASSERT` and writes a JSONL audit record. Push degrades to exit 0 so recovery work is not bricked; merge holds with retryable exit 75 until the provider recovers, then self-clears without manual reset. Neither outcome is evidence that CI was clear. For a repository with no CI configured at all, `pr-merge.sh --no-ci-expected` is the sanctioned merge path: it forwards to `ci-queue-wait.sh --no-ci-expected`, which reclassifies a zero-context merge head as queue-clear only when the acting token holds repository admin and `MOSAIC_GIT_IDENTITY` names the asserting identity (a caller without one is refused with exit 78 before the admin lookup), and records the assertion (or its refusal) in the same JSONL audit log. `pr-merge.sh` automatically inspects the exact PR head repository and full commit SHA rather than its `main` base; this also handles fork PRs without branch-name ambiguity. Pass `--expect-head <approved-full-sha>` to bind a commit-specific review or merge-gate verdict; Gitea uses atomic `head_commit_id` and GitHub uses `--match-head-commit`.
|
||||||
|
|
||||||
### Code Review (Codex)
|
### Code Review (Codex)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# ci-queue-wait.sh - Wait until project CI queue is clear (no running/queued pipeline on branch head)
|
# ci-queue-wait.sh - Wait until project CI queue is clear (no running/queued pipeline on branch head)
|
||||||
# Usage: ci-queue-wait.sh [-B branch] [-t timeout_sec] [-i interval_sec] [--purpose push|merge] [--require-status]
|
# Usage: ci-queue-wait.sh [-B branch] [-t timeout_sec] [-i interval_sec] [--purpose push|merge] [--require-status] [--no-ci-expected]
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@@ -14,10 +14,11 @@ TIMEOUT_SEC=900
|
|||||||
INTERVAL_SEC=15
|
INTERVAL_SEC=15
|
||||||
PURPOSE="merge"
|
PURPOSE="merge"
|
||||||
REQUIRE_STATUS=0
|
REQUIRE_STATUS=0
|
||||||
|
NO_CI_EXPECTED=0
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
Usage: $(basename "$0") [-B branch] [-R owner/repo] [--sha full-40] [-t timeout_sec] [-i interval_sec] [--purpose push|merge] [--require-status]
|
Usage: $(basename "$0") [-B branch] [-R owner/repo] [--sha full-40] [-t timeout_sec] [-i interval_sec] [--purpose push|merge] [--require-status] [--no-ci-expected]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-B, --branch BRANCH Branch head to inspect (default: current branch)
|
-B, --branch BRANCH Branch head to inspect (default: current branch)
|
||||||
@@ -27,6 +28,7 @@ Options:
|
|||||||
-i, --interval SECONDS Poll interval in seconds (default: 15)
|
-i, --interval SECONDS Poll interval in seconds (default: 15)
|
||||||
--purpose VALUE Log context: push|merge (default: merge)
|
--purpose VALUE Log context: push|merge (default: merge)
|
||||||
--require-status Fail if no CI status contexts are present
|
--require-status Fail if no CI status contexts are present
|
||||||
|
--no-ci-expected Assert this repository has no CI configured: a merge guard on a zero-context head becomes queue-clear (requires the acting token to hold repository admin); refused with exit 78 when MOSAIC_GIT_IDENTITY is unset or empty
|
||||||
-h, --help Show this help
|
-h, --help Show this help
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
@@ -175,6 +177,50 @@ PY
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Durable audit record for an explicit no-CI assertion event (granted or
|
||||||
|
# refused). Same JSONL sink and field shape as record_cannot_assert so one
|
||||||
|
# reader covers all three outcomes; the outcome value distinguishes them.
|
||||||
|
# rc 70 on an unwritable sink: a merge pass that cannot be audited must not
|
||||||
|
# be reachable, mirroring record_cannot_assert's refusal of a degraded pass.
|
||||||
|
record_assertion_event() {
|
||||||
|
local outcome="$1" reason="$2" asserted_by="$3"
|
||||||
|
local audit_log="${MOSAIC_CI_QUEUE_AUDIT_LOG:-${XDG_STATE_HOME:-${HOME:-}/.local/state}/mosaic/audit/ci-queue-wait.jsonl}"
|
||||||
|
|
||||||
|
if [[ -z "$audit_log" ]] || ! mkdir -p "$(dirname "$audit_log")"; then
|
||||||
|
echo "Error: could not write ${outcome} audit record (audit directory unavailable at ${audit_log})." >&2
|
||||||
|
return 70
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! python3 - "$audit_log" "$outcome" "$reason" "$asserted_by" "${PLATFORM:-unknown}" "$PURPOSE" "${BRANCH:-unknown}" "${OWNER:-unknown}/${REPO:-unknown}" <<'PY'
|
||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
path, outcome, reason, asserted_by, platform, purpose, branch, repo = sys.argv[1:]
|
||||||
|
record = {
|
||||||
|
"timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
||||||
|
"outcome": outcome,
|
||||||
|
"reason": reason,
|
||||||
|
"platform": platform,
|
||||||
|
"purpose": purpose,
|
||||||
|
"branch": branch,
|
||||||
|
"repo": repo,
|
||||||
|
"asserted_by": asserted_by,
|
||||||
|
}
|
||||||
|
fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o600)
|
||||||
|
try:
|
||||||
|
os.write(fd, (json.dumps(record, separators=(",", ":")) + "\n").encode())
|
||||||
|
finally:
|
||||||
|
os.close(fd)
|
||||||
|
PY
|
||||||
|
then
|
||||||
|
echo "Error: could not write ${outcome} audit record at ${audit_log}; refusing to proceed unaudited." >&2
|
||||||
|
return 70
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
github_get_branch_head_sha() {
|
github_get_branch_head_sha() {
|
||||||
local owner="$1"
|
local owner="$1"
|
||||||
local repo="$2"
|
local repo="$2"
|
||||||
@@ -182,6 +228,24 @@ github_get_branch_head_sha() {
|
|||||||
gh api "repos/${owner}/${repo}/branches/${branch}" --jq '.commit.sha'
|
gh api "repos/${owner}/${repo}/branches/${branch}" --jq '.commit.sha'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Repository-admin state for the acting credential, GitHub flavor. The
|
||||||
|
# repository object's permissions.admin is the field; read through the same
|
||||||
|
# gh CLI the guard already authenticates with. rc 0 = admin, 1 = not admin
|
||||||
|
# (or field absent), 2 = indeterminate (transport/API failure).
|
||||||
|
github_repo_admin_state() {
|
||||||
|
local owner="$1"
|
||||||
|
local repo="$2"
|
||||||
|
local perm
|
||||||
|
if ! perm=$(gh api "repos/${owner}/${repo}" --jq '.permissions.admin' 2>/dev/null); then
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
case "$perm" in
|
||||||
|
true) return 0 ;;
|
||||||
|
false|null|"") return 1 ;;
|
||||||
|
*) return 2 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
github_get_commit_status_json() {
|
github_get_commit_status_json() {
|
||||||
local owner="$1"
|
local owner="$1"
|
||||||
local repo="$2"
|
local repo="$2"
|
||||||
@@ -306,6 +370,41 @@ gitea_get_commit_status_json() {
|
|||||||
curl -fsSL -H "User-Agent: curl/8" -H "Authorization: token ${token}" "$url"
|
curl -fsSL -H "User-Agent: curl/8" -H "Authorization: token ${token}" "$url"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Repository-admin state for the acting credential, Gitea flavor. The guard's
|
||||||
|
# existing fetches (branch head, combined status) carry no permissions object
|
||||||
|
# (measured: neither response includes one), so the elevation check reads the
|
||||||
|
# repository object's permissions.admin, the one documented carrier of that
|
||||||
|
# field. rc 0 = admin, 1 = not admin (or field absent), 2 = indeterminate
|
||||||
|
# (non-200 or unparseable).
|
||||||
|
gitea_repo_admin_state() {
|
||||||
|
local host="$1"
|
||||||
|
local repo="$2"
|
||||||
|
local token="$3"
|
||||||
|
local url="https://${host}/api/v1/repos/${repo}"
|
||||||
|
local resp code body
|
||||||
|
resp=$(curl -sS -H "User-Agent: curl/8" -H "Authorization: token ${token}" -w $'\n%{http_code}' "$url") || return 2
|
||||||
|
code="${resp##*$'\n'}"
|
||||||
|
body="${resp%$'\n'*}"
|
||||||
|
if [[ "$code" != "200" ]]; then
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
printf '%s' "$body" | python3 -c '
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
try:
|
||||||
|
payload = json.load(sys.stdin)
|
||||||
|
except Exception:
|
||||||
|
raise SystemExit(2)
|
||||||
|
if not isinstance(payload, dict):
|
||||||
|
raise SystemExit(2)
|
||||||
|
permissions = payload.get("permissions")
|
||||||
|
if not isinstance(permissions, dict) or permissions.get("admin") is not True:
|
||||||
|
raise SystemExit(1)
|
||||||
|
raise SystemExit(0)
|
||||||
|
'
|
||||||
|
}
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
-B|--branch)
|
-B|--branch)
|
||||||
@@ -336,6 +435,10 @@ while [[ $# -gt 0 ]]; do
|
|||||||
REQUIRE_STATUS=1
|
REQUIRE_STATUS=1
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
--no-ci-expected)
|
||||||
|
NO_CI_EXPECTED=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
-h|--help)
|
-h|--help)
|
||||||
usage
|
usage
|
||||||
exit 0
|
exit 0
|
||||||
@@ -365,6 +468,10 @@ if [[ "$PURPOSE" != "push" && "$PURPOSE" != "merge" ]]; then
|
|||||||
echo "Error: --purpose must be push or merge." >&2
|
echo "Error: --purpose must be push or merge." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
if [[ "$NO_CI_EXPECTED" -eq 1 && "$REQUIRE_STATUS" -eq 1 ]]; then
|
||||||
|
echo "Error: --no-ci-expected and --require-status contradict each other: one asserts the repository has no CI, the other demands status contexts. Pass at most one." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
OWNER="unknown"
|
OWNER="unknown"
|
||||||
REPO="unknown"
|
REPO="unknown"
|
||||||
@@ -484,6 +591,48 @@ while true; do
|
|||||||
echo "[ci-queue-wait] queue-clear state=no-status purpose=push branch=${BRANCH}; no queued or running CI."
|
echo "[ci-queue-wait] queue-clear state=no-status purpose=push branch=${BRANCH}; no queued or running CI."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
if [[ "$NO_CI_EXPECTED" -eq 1 ]]; then
|
||||||
|
# Explicit, elevated, audit-visible assertion that this
|
||||||
|
# repository has no CI to wait on. The zero-context case is
|
||||||
|
# the ONLY state the flag reclassifies: a pending or failed
|
||||||
|
# context still holds or fails exactly as without it, and a
|
||||||
|
# non-admin token is refused rather than trusted.
|
||||||
|
# The assertion must name an asserting identity: "unknown"
|
||||||
|
# attributes nothing, so a caller with MOSAIC_GIT_IDENTITY
|
||||||
|
# unset or empty is refused (exit 78) BEFORE the permission
|
||||||
|
# lookup -- an unattributable caller never triggers that
|
||||||
|
# network call.
|
||||||
|
if [[ -z "${MOSAIC_GIT_IDENTITY:-}" ]]; then
|
||||||
|
record_assertion_event "ASSERTION_UNATTRIBUTABLE" "actor-unattributable" "unknown" \
|
||||||
|
|| echo "Warning: could not write the ASSERTION_UNATTRIBUTABLE audit record; the refusal itself stands." >&2
|
||||||
|
echo "Error: ASSERTION_UNATTRIBUTABLE state=no-status purpose=merge asserted-by=unknown reason=no-ci-expected branch=${BRANCH}; --no-ci-expected requires MOSAIC_GIT_IDENTITY to name the asserting identity and it is unset or empty (exit 78)." >&2
|
||||||
|
exit 78
|
||||||
|
fi
|
||||||
|
ASSERTED_BY="${MOSAIC_GIT_IDENTITY}"
|
||||||
|
ADMIN_STATE=2
|
||||||
|
if [[ "$PLATFORM" == "github" ]]; then
|
||||||
|
if github_repo_admin_state "$OWNER" "$REPO"; then ADMIN_STATE=0; else ADMIN_STATE=$?; fi
|
||||||
|
else
|
||||||
|
if gitea_repo_admin_state "$HOST" "$OWNER/$REPO" "$TOKEN"; then ADMIN_STATE=0; else ADMIN_STATE=$?; fi
|
||||||
|
fi
|
||||||
|
case "$ADMIN_STATE" in
|
||||||
|
0)
|
||||||
|
record_assertion_event "NO_CI_ASSERTED" "no-ci-expected" "$ASSERTED_BY" || exit $?
|
||||||
|
echo "[ci-queue-wait] queue-clear state=no-status purpose=merge asserted-by=${ASSERTED_BY} reason=no-ci-expected branch=${BRANCH}"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
1)
|
||||||
|
record_assertion_event "ASSERTION_REFUSED" "actor-not-repo-admin" "$ASSERTED_BY" \
|
||||||
|
|| echo "Warning: could not write the ASSERTION_REFUSED audit record; the refusal itself stands." >&2
|
||||||
|
echo "Error: ASSERTION_REFUSED state=no-status purpose=merge asserted-by=${ASSERTED_BY} reason=no-ci-expected branch=${BRANCH}; --no-ci-expected requires repository admin and the acting token is not an admin of ${OWNER}/${REPO} (exit 77)." >&2
|
||||||
|
exit 77
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
record_cannot_assert "repo-permissions-unavailable"
|
||||||
|
exit $?
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
echo "Error: ASSERTED_NOT_READY state=no-status purpose=${PURPOSE} branch=${BRANCH}." >&2
|
echo "Error: ASSERTED_NOT_READY state=no-status purpose=${PURPOSE} branch=${BRANCH}." >&2
|
||||||
exit 3
|
exit 3
|
||||||
;;
|
;;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# pr-merge.sh - Merge pull requests on Gitea or GitHub
|
# pr-merge.sh - Merge pull requests on Gitea or GitHub
|
||||||
# Usage: pr-merge.sh -n PR_NUMBER [-m squash] [-d] [--expect-head SHA] [--co-author-trailers --escalate-to PRINCIPAL]
|
# Usage: pr-merge.sh -n PR_NUMBER [-m squash] [-d] [--expect-head SHA] [--no-ci-expected] [--co-author-trailers --escalate-to PRINCIPAL]
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@@ -16,6 +16,7 @@ DRY_RUN=false
|
|||||||
EXPECT_HEAD=""
|
EXPECT_HEAD=""
|
||||||
CO_AUTHOR_TRAILERS=false
|
CO_AUTHOR_TRAILERS=false
|
||||||
ESCALATE_TO=""
|
ESCALATE_TO=""
|
||||||
|
NO_CI_EXPECTED=false
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
@@ -29,6 +30,7 @@ Options:
|
|||||||
-d, --delete-branch Delete the head branch after merge
|
-d, --delete-branch Delete the head branch after merge
|
||||||
--dry-run Run metadata/login preflight without merging
|
--dry-run Run metadata/login preflight without merging
|
||||||
--expect-head SHA Refuse unless the PR head matches this full commit SHA
|
--expect-head SHA Refuse unless the PR head matches this full commit SHA
|
||||||
|
--no-ci-expected Assert the target repository has no CI: forward --no-ci-expected to the queue guard (requires repository admin)
|
||||||
--co-author-trailers Build verified trailers from linked PR commit authors
|
--co-author-trailers Build verified trailers from linked PR commit authors
|
||||||
--escalate-to NAME Named principal for an unresolved-author BLOCK
|
--escalate-to NAME Named principal for an unresolved-author BLOCK
|
||||||
-h, --help Show this help message
|
-h, --help Show this help message
|
||||||
@@ -70,6 +72,10 @@ while [[ $# -gt 0 ]]; do
|
|||||||
EXPECT_HEAD="$2"
|
EXPECT_HEAD="$2"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
|
--no-ci-expected)
|
||||||
|
NO_CI_EXPECTED=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
--co-author-trailers)
|
--co-author-trailers)
|
||||||
CO_AUTHOR_TRAILERS=true
|
CO_AUTHOR_TRAILERS=true
|
||||||
shift
|
shift
|
||||||
@@ -154,13 +160,18 @@ if [[ "$DRY_RUN" != true ]]; then
|
|||||||
if [[ -z "$BASE_REPO" ]]; then
|
if [[ -z "$BASE_REPO" ]]; then
|
||||||
BASE_REPO="$(get_repo_owner)/$(get_repo_name)"
|
BASE_REPO="$(get_repo_owner)/$(get_repo_name)"
|
||||||
fi
|
fi
|
||||||
"$SCRIPT_DIR/ci-queue-wait.sh" \
|
guard_args=(
|
||||||
--purpose merge \
|
--purpose merge
|
||||||
-B "$HEAD_BRANCH" \
|
-B "$HEAD_BRANCH"
|
||||||
-R "$BASE_REPO" \
|
-R "$BASE_REPO"
|
||||||
--sha "$HEAD_SHA" \
|
--sha "$HEAD_SHA"
|
||||||
-t "${MOSAIC_CI_QUEUE_TIMEOUT_SEC:-900}" \
|
-t "${MOSAIC_CI_QUEUE_TIMEOUT_SEC:-900}"
|
||||||
-i "${MOSAIC_CI_QUEUE_POLL_SEC:-15}"
|
-i "${MOSAIC_CI_QUEUE_POLL_SEC:-15}"
|
||||||
|
)
|
||||||
|
if [[ "$NO_CI_EXPECTED" == true ]]; then
|
||||||
|
guard_args+=(--no-ci-expected)
|
||||||
|
fi
|
||||||
|
"$SCRIPT_DIR/ci-queue-wait.sh" "${guard_args[@]}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
PLATFORM=$(detect_platform)
|
PLATFORM=$(detect_platform)
|
||||||
|
|||||||
@@ -0,0 +1,323 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Regression harness for ci-queue-wait.sh's --no-ci-expected assertion:
|
||||||
|
# the sanctioned merge path for a repository with no CI configured at all.
|
||||||
|
#
|
||||||
|
# Zero status contexts ("no-status") stays fail-closed for --purpose merge
|
||||||
|
# by default, because at merge time no-status can also mean "CI has not
|
||||||
|
# reported yet". --no-ci-expected reclassifies ONLY that zero-context case
|
||||||
|
# as queue-clear, and only for a caller whose acting token holds repository
|
||||||
|
# admin. This harness pins:
|
||||||
|
# (a) merge + no-status + flag + admin -> exit 0, audit line + JSONL.
|
||||||
|
# (b) merge + no-status, no flag -> exit 3, existing text (unchanged).
|
||||||
|
# (c) merge + no-status + flag + non-admin -> exit 77 ASSERTION_REFUSED
|
||||||
|
# (distinct text, exit code NOT 3) + JSONL refusal record.
|
||||||
|
# (c2) flag + admin payload without the admin field -> fail closed as (c).
|
||||||
|
# (d) flag + --require-status -> usage error, before any network.
|
||||||
|
# (e) flag + a real pending context -> still holds (timeout 124),
|
||||||
|
# and the admin endpoint is never consulted.
|
||||||
|
# (f) push + no-status, with and without the flag -> push queue-clear
|
||||||
|
# unchanged; no admin consultation on push.
|
||||||
|
# (g) flag + admin lookup unreachable -> CANNOT_ASSERT hold (75),
|
||||||
|
# not a silent pass and not a refusal.
|
||||||
|
# (h) flag + admin stub + NO MOSAIC_GIT_IDENTITY -> refusal BEFORE
|
||||||
|
# queue-clear and BEFORE the admin lookup: exit 78, no queue-clear
|
||||||
|
# line, an ASSERTION_UNATTRIBUTABLE JSONL record, no repos/ call.
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
WORK_DIR="${MOSAIC_TEST_WORK_DIR:-$PWD/.mosaic-test-work/ci-queue-wait-no-ci-expected}"
|
||||||
|
REPO_DIR="$WORK_DIR/repo"
|
||||||
|
STUB_DIR="$WORK_DIR/stubs"
|
||||||
|
URL_LOG="$WORK_DIR/urls.log"
|
||||||
|
|
||||||
|
rm -rf "$WORK_DIR"
|
||||||
|
mkdir -p "$REPO_DIR" "$STUB_DIR"
|
||||||
|
|
||||||
|
git -C "$REPO_DIR" init -q
|
||||||
|
git -C "$REPO_DIR" remote add origin https://git.example.test/acme/widgets.git
|
||||||
|
|
||||||
|
# Same stub conventions as test-ci-queue-wait-no-status.sh; adds the
|
||||||
|
# repository-object endpoint (admin state) selected by MOSAIC_STUB_ADMIN_MODE.
|
||||||
|
cat > "$STUB_DIR/curl" <<'SH'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
has_w=0
|
||||||
|
url=""
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
-w) has_w=1 ;;
|
||||||
|
http://*|https://*) url="$arg" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
printf '%s\n' "$url" >> "${MOSAIC_STUB_URL_LOG:?}"
|
||||||
|
|
||||||
|
case "$url" in
|
||||||
|
*/branches/*)
|
||||||
|
body='{"commit":{"id":"deadbeefcafef00d0123456789abcdef01234567"}}'
|
||||||
|
if [[ "$has_w" == 1 ]]; then
|
||||||
|
printf '%s\n200' "$body"
|
||||||
|
else
|
||||||
|
printf '%s' "$body"
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*/status)
|
||||||
|
mode="${MOSAIC_STUB_STATUS_MODE:?MOSAIC_STUB_STATUS_MODE not set}"
|
||||||
|
case "$mode" in
|
||||||
|
no-status) body='{"state":"","statuses":[]}' ;;
|
||||||
|
real-pending) body='{"state":"pending","statuses":[{"context":"ci/woodpecker","status":"running","target_url":""}]}' ;;
|
||||||
|
*) echo "curl stub: unknown status mode=$mode" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
printf '%s' "$body"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*/repos/*)
|
||||||
|
mode="${MOSAIC_STUB_ADMIN_MODE:?MOSAIC_STUB_ADMIN_MODE not set}"
|
||||||
|
case "$mode" in
|
||||||
|
admin) body='{"permissions":{"admin":true,"push":true,"pull":true}}' ;;
|
||||||
|
non-admin) body='{"permissions":{"admin":false,"push":true,"pull":true}}' ;;
|
||||||
|
no-admin-field) body='{"permissions":{}}' ;;
|
||||||
|
unreachable) exit 7 ;;
|
||||||
|
*) echo "curl stub: unknown admin mode=$mode" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
if [[ "$has_w" == 1 ]]; then
|
||||||
|
printf '%s\n200' "$body"
|
||||||
|
else
|
||||||
|
printf '%s' "$body"
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "curl stub: unrecognized URL: $url" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
SH
|
||||||
|
chmod +x "$STUB_DIR/curl"
|
||||||
|
|
||||||
|
failures=0
|
||||||
|
|
||||||
|
run_guard() {
|
||||||
|
local name="$1"; shift
|
||||||
|
(
|
||||||
|
cd "$REPO_DIR" || exit
|
||||||
|
export PATH="$STUB_DIR:$PATH"
|
||||||
|
export MOSAIC_CREDENTIALS_FILE="$WORK_DIR/no-credentials.json"
|
||||||
|
export MOSAIC_CI_QUEUE_AUDIT_LOG="$WORK_DIR/audit-$name.jsonl"
|
||||||
|
export MOSAIC_STUB_URL_LOG="$URL_LOG"
|
||||||
|
export GITEA_TOKEN="stub-token"
|
||||||
|
export GITEA_URL="https://git.example.test"
|
||||||
|
export MOSAIC_GIT_IDENTITY="test-identity"
|
||||||
|
"$SCRIPT_DIR/ci-queue-wait.sh" -B main -t 3 -i 1 "$@"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
# The suite exports test-identity globally, so the unattributable-caller
|
||||||
|
# case must strip it from the child environment at invocation with env -u,
|
||||||
|
# not rely on the export order.
|
||||||
|
run_guard_no_identity() {
|
||||||
|
local name="$1"; shift
|
||||||
|
(
|
||||||
|
cd "$REPO_DIR" || exit
|
||||||
|
export PATH="$STUB_DIR:$PATH"
|
||||||
|
export MOSAIC_CREDENTIALS_FILE="$WORK_DIR/no-credentials.json"
|
||||||
|
export MOSAIC_CI_QUEUE_AUDIT_LOG="$WORK_DIR/audit-$name.jsonl"
|
||||||
|
export MOSAIC_STUB_URL_LOG="$URL_LOG"
|
||||||
|
export GITEA_TOKEN="stub-token"
|
||||||
|
export GITEA_URL="https://git.example.test"
|
||||||
|
export MOSAIC_GIT_IDENTITY="test-identity"
|
||||||
|
env -u MOSAIC_GIT_IDENTITY \
|
||||||
|
"$SCRIPT_DIR/ci-queue-wait.sh" -B main -t 3 -i 1 "$@"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
expect_rc() {
|
||||||
|
local name="$1" want="$2" got="$3"
|
||||||
|
if [[ "$want" == "not3" ]]; then
|
||||||
|
if [[ "$got" -eq 0 || "$got" -eq 3 ]]; then
|
||||||
|
echo "FAIL $name: expected a refusal rc (nonzero, not 3), got $got" >&2
|
||||||
|
failures=$((failures + 1))
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
elif [[ "$got" -ne "$want" ]]; then
|
||||||
|
echo "FAIL $name: expected rc=$want, got rc=$got" >&2
|
||||||
|
failures=$((failures + 1))
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
expect_text() {
|
||||||
|
local name="$1" want="$2" output="$3" polarity="${4:-present}"
|
||||||
|
if [[ "$polarity" == "present" && "$output" != *"$want"* ]]; then
|
||||||
|
echo "FAIL $name: output missing '$want'" >&2
|
||||||
|
printf '%s\n' "$output" >&2
|
||||||
|
failures=$((failures + 1))
|
||||||
|
elif [[ "$polarity" == "absent" && "$output" == *"$want"* ]]; then
|
||||||
|
echo "FAIL $name: output unexpectedly contains '$want'" >&2
|
||||||
|
printf '%s\n' "$output" >&2
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
repo_root_fetched() {
|
||||||
|
grep -q 'repos/acme/widgets$' "$URL_LOG"
|
||||||
|
}
|
||||||
|
|
||||||
|
# (a) merge + no-status + flag + admin -> exit 0, assertion line, JSONL record.
|
||||||
|
: > "$URL_LOG"
|
||||||
|
set +e
|
||||||
|
out_a=$(MOSAIC_STUB_STATUS_MODE=no-status MOSAIC_STUB_ADMIN_MODE=admin run_guard a --purpose merge --no-ci-expected 2>&1)
|
||||||
|
rc_a=$?
|
||||||
|
set -u
|
||||||
|
if expect_rc a 0 "$rc_a"; then
|
||||||
|
expect_text a "queue-clear state=no-status purpose=merge asserted-by=test-identity reason=no-ci-expected branch=main" "$out_a"
|
||||||
|
expect_text a "ASSERTED_NOT_READY" "$out_a" absent
|
||||||
|
if ! grep -q '"outcome":"NO_CI_ASSERTED"' "$WORK_DIR/audit-a.jsonl" 2>/dev/null; then
|
||||||
|
echo "FAIL a: expected a NO_CI_ASSERTED JSONL audit record" >&2
|
||||||
|
failures=$((failures + 1))
|
||||||
|
elif ! grep -q '"asserted_by":"test-identity"' "$WORK_DIR/audit-a.jsonl"; then
|
||||||
|
echo "FAIL a: audit record does not name the asserting identity" >&2
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (b) merge + no-status, no flag -> exit 3, existing error text unchanged.
|
||||||
|
: > "$URL_LOG"
|
||||||
|
set +e
|
||||||
|
out_b=$(MOSAIC_STUB_STATUS_MODE=no-status run_guard b --purpose merge 2>&1)
|
||||||
|
rc_b=$?
|
||||||
|
set -u
|
||||||
|
if expect_rc b 3 "$rc_b"; then
|
||||||
|
expect_text b "Error: ASSERTED_NOT_READY state=no-status purpose=merge branch=main." "$out_b"
|
||||||
|
expect_text b "asserted-by" "$out_b" absent
|
||||||
|
fi
|
||||||
|
if repo_root_fetched; then
|
||||||
|
echo "FAIL b: admin endpoint consulted without the flag" >&2
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (c) merge + no-status + flag + non-admin -> distinct refusal, rc NOT 3.
|
||||||
|
: > "$URL_LOG"
|
||||||
|
set +e
|
||||||
|
out_c=$(MOSAIC_STUB_STATUS_MODE=no-status MOSAIC_STUB_ADMIN_MODE=non-admin run_guard c --purpose merge --no-ci-expected 2>&1)
|
||||||
|
rc_c=$?
|
||||||
|
set -u
|
||||||
|
if expect_rc c not3 "$rc_c"; then
|
||||||
|
if [[ "$rc_c" -ne 77 ]]; then
|
||||||
|
echo "FAIL c: expected the documented refusal rc=77, got $rc_c" >&2
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
expect_text c "ASSERTION_REFUSED state=no-status purpose=merge asserted-by=test-identity reason=no-ci-expected branch=main" "$out_c"
|
||||||
|
expect_text c "ASSERTED_NOT_READY" "$out_c" absent
|
||||||
|
if ! grep -q '"outcome":"ASSERTION_REFUSED"' "$WORK_DIR/audit-c.jsonl" 2>/dev/null; then
|
||||||
|
echo "FAIL c: expected an ASSERTION_REFUSED JSONL audit record" >&2
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (c2) admin payload with no admin field -> fail closed as non-admin.
|
||||||
|
: > "$URL_LOG"
|
||||||
|
set +e
|
||||||
|
out_c2=$(MOSAIC_STUB_STATUS_MODE=no-status MOSAIC_STUB_ADMIN_MODE=no-admin-field run_guard c2 --purpose merge --no-ci-expected 2>&1)
|
||||||
|
rc_c2=$?
|
||||||
|
set -u
|
||||||
|
if expect_rc c2 77 "$rc_c2"; then
|
||||||
|
expect_text c2 "ASSERTION_REFUSED" "$out_c2"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (d) flag + --require-status -> usage error before any network I/O.
|
||||||
|
: > "$URL_LOG"
|
||||||
|
set +e
|
||||||
|
out_d=$(MOSAIC_STUB_STATUS_MODE=no-status MOSAIC_STUB_ADMIN_MODE=admin run_guard d --purpose merge --no-ci-expected --require-status 2>&1)
|
||||||
|
rc_d=$?
|
||||||
|
set -u
|
||||||
|
if expect_rc d 1 "$rc_d"; then
|
||||||
|
expect_text d "--no-ci-expected and --require-status contradict" "$out_d"
|
||||||
|
fi
|
||||||
|
if [[ -s "$URL_LOG" ]]; then
|
||||||
|
echo "FAIL d: usage error must precede every network call" >&2
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (e) flag + a real pending context -> still holds; admin endpoint never asked.
|
||||||
|
: > "$URL_LOG"
|
||||||
|
set +e
|
||||||
|
out_e=$(MOSAIC_STUB_STATUS_MODE=real-pending MOSAIC_STUB_ADMIN_MODE=admin run_guard e --purpose merge --no-ci-expected 2>&1)
|
||||||
|
rc_e=$?
|
||||||
|
set -u
|
||||||
|
if expect_rc e 124 "$rc_e"; then
|
||||||
|
expect_text e "ASSERTED_NOT_READY" "$out_e"
|
||||||
|
expect_text e "ci/woodpecker=running" "$out_e"
|
||||||
|
fi
|
||||||
|
if repo_root_fetched; then
|
||||||
|
echo "FAIL e: a pending context must not trigger the admin assertion" >&2
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (f) push + no-status stays queue-clear, with and without the flag.
|
||||||
|
: > "$URL_LOG"
|
||||||
|
set +e
|
||||||
|
out_f=$(MOSAIC_STUB_STATUS_MODE=no-status MOSAIC_STUB_ADMIN_MODE=non-admin run_guard f --purpose push 2>&1)
|
||||||
|
rc_f=$?
|
||||||
|
set -u
|
||||||
|
if expect_rc f 0 "$rc_f"; then
|
||||||
|
expect_text f "queue-clear state=no-status purpose=push branch=main; no queued or running CI." "$out_f"
|
||||||
|
fi
|
||||||
|
: > "$URL_LOG"
|
||||||
|
set +e
|
||||||
|
out_f2=$(MOSAIC_STUB_STATUS_MODE=no-status MOSAIC_STUB_ADMIN_MODE=non-admin run_guard f2 --purpose push --no-ci-expected 2>&1)
|
||||||
|
rc_f2=$?
|
||||||
|
set -u
|
||||||
|
if expect_rc f2 0 "$rc_f2"; then
|
||||||
|
expect_text f2 "queue-clear state=no-status purpose=push branch=main; no queued or running CI." "$out_f2"
|
||||||
|
expect_text f2 "asserted-by" "$out_f2" absent
|
||||||
|
fi
|
||||||
|
if repo_root_fetched; then
|
||||||
|
echo "FAIL f: push must not consult the admin endpoint" >&2
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (g) flag + admin lookup unreachable -> CANNOT_ASSERT hold (75), not a pass.
|
||||||
|
: > "$URL_LOG"
|
||||||
|
set +e
|
||||||
|
out_g=$(MOSAIC_STUB_STATUS_MODE=no-status MOSAIC_STUB_ADMIN_MODE=unreachable run_guard g --purpose merge --no-ci-expected 2>&1)
|
||||||
|
rc_g=$?
|
||||||
|
set -u
|
||||||
|
if expect_rc g 75 "$rc_g"; then
|
||||||
|
expect_text g "CANNOT_ASSERT reason=repo-permissions-unavailable" "$out_g"
|
||||||
|
fi
|
||||||
|
if ! grep -q '"outcome":"CANNOT_ASSERT"' "$WORK_DIR/audit-g.jsonl" 2>/dev/null; then
|
||||||
|
echo "FAIL g: expected a CANNOT_ASSERT JSONL audit record" >&2
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# (h) flag + admin stub + no asserting identity -> refusal before queue-clear
|
||||||
|
# and before the admin lookup: rc 78, no queue-clear line, an
|
||||||
|
# ASSERTION_UNATTRIBUTABLE JSONL record, and zero repos/ network calls.
|
||||||
|
: > "$URL_LOG"
|
||||||
|
set +e
|
||||||
|
out_h=$(MOSAIC_STUB_STATUS_MODE=no-status MOSAIC_STUB_ADMIN_MODE=admin run_guard_no_identity h --purpose merge --no-ci-expected 2>&1)
|
||||||
|
rc_h=$?
|
||||||
|
set -u
|
||||||
|
if expect_rc h 78 "$rc_h"; then
|
||||||
|
expect_text h "ASSERTION_UNATTRIBUTABLE state=no-status purpose=merge asserted-by=unknown reason=no-ci-expected branch=main" "$out_h"
|
||||||
|
expect_text h "queue-clear" "$out_h" absent
|
||||||
|
if ! grep -q '"outcome":"ASSERTION_UNATTRIBUTABLE"' "$WORK_DIR/audit-h.jsonl" 2>/dev/null; then
|
||||||
|
echo "FAIL h: expected an ASSERTION_UNATTRIBUTABLE JSONL audit record" >&2
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if repo_root_fetched; then
|
||||||
|
echo "FAIL h: an unattributable caller must not trigger the permission lookup" >&2
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$failures" -ne 0 ]]; then
|
||||||
|
echo "ci-queue-wait no-ci-expected regression failed ($failures assertions)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "ci-queue-wait no-ci-expected regression passed (all outcome classes)"
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# pr-merge must forward --no-ci-expected to the queue guard, and only then.
|
||||||
|
# The flag is the sanctioned merge path for a repository with no CI configured
|
||||||
|
# (see test-ci-queue-wait-no-ci-expected.sh for the guard-side semantics);
|
||||||
|
# this harness pins only the pass-through: present when requested, absent when
|
||||||
|
# not, with the rest of the guard invocation unchanged.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
WORK_DIR="${MOSAIC_TEST_WORK_DIR:-$PWD/.mosaic-test-work/pr-merge-no-ci-expected}"
|
||||||
|
FIXTURE_DIR="$WORK_DIR/tools/git"
|
||||||
|
CALL_LOG="$WORK_DIR/queue-call.log"
|
||||||
|
|
||||||
|
rm -rf "$WORK_DIR"
|
||||||
|
mkdir -p "$FIXTURE_DIR"
|
||||||
|
cp "$SCRIPT_DIR/pr-merge.sh" "$FIXTURE_DIR/pr-merge.sh"
|
||||||
|
cp "$SCRIPT_DIR/detect-platform.sh" "$FIXTURE_DIR/detect-platform.sh"
|
||||||
|
|
||||||
|
cat > "$FIXTURE_DIR/pr-metadata.sh" <<'SH'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
printf '%s\n' '{"baseRefName":"main","baseRepository":"mosaicstack/stack","headRefName":"fix/no-ci-fixture","headRefOid":"0123456789abcdef0123456789abcdef01234567","headRepository":"mosaicstack/stack"}'
|
||||||
|
SH
|
||||||
|
|
||||||
|
cat > "$FIXTURE_DIR/ci-queue-wait.sh" <<'SH'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
printf '%s\n' "$*" > "${MOSAIC_QUEUE_CALL_LOG:?}"
|
||||||
|
exit 42
|
||||||
|
SH
|
||||||
|
chmod +x "$FIXTURE_DIR"/*.sh
|
||||||
|
|
||||||
|
run_merge() {
|
||||||
|
(
|
||||||
|
cd "$WORK_DIR"
|
||||||
|
export MOSAIC_QUEUE_CALL_LOG="$CALL_LOG"
|
||||||
|
"$FIXTURE_DIR/pr-merge.sh" -n 123 "$@"
|
||||||
|
) >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
fail=0
|
||||||
|
|
||||||
|
# With the flag: it must reach the guard invocation.
|
||||||
|
: > "$CALL_LOG"
|
||||||
|
set +e
|
||||||
|
run_merge --no-ci-expected
|
||||||
|
rc_with=$?
|
||||||
|
set -e
|
||||||
|
if [[ "$rc_with" -ne 42 ]]; then
|
||||||
|
echo "FAIL(with): expected queue stub rc=42 to propagate, got $rc_with" >&2
|
||||||
|
fail=1
|
||||||
|
elif ! grep -q -- '--no-ci-expected' "$CALL_LOG"; then
|
||||||
|
echo "FAIL(with): --no-ci-expected did not reach the queue guard" >&2
|
||||||
|
cat "$CALL_LOG" >&2
|
||||||
|
fail=1
|
||||||
|
fi
|
||||||
|
# The rest of the guard invocation is unchanged by the flag.
|
||||||
|
for required in '--purpose merge' '-B fix/no-ci-fixture' '-R mosaicstack/stack' \
|
||||||
|
'--sha 0123456789abcdef0123456789abcdef01234567'; do
|
||||||
|
if ! grep -qF -- "$required" "$CALL_LOG"; then
|
||||||
|
echo "FAIL(with): guard invocation lost '$required'" >&2
|
||||||
|
cat "$CALL_LOG" >&2
|
||||||
|
fail=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Without the flag: it must NOT appear in the guard invocation.
|
||||||
|
: > "$CALL_LOG"
|
||||||
|
set +e
|
||||||
|
run_merge
|
||||||
|
rc_without=$?
|
||||||
|
set -e
|
||||||
|
if [[ "$rc_without" -ne 42 ]]; then
|
||||||
|
echo "FAIL(without): expected queue stub rc=42 to propagate, got $rc_without" >&2
|
||||||
|
fail=1
|
||||||
|
elif grep -q -- '--no-ci-expected' "$CALL_LOG"; then
|
||||||
|
echo "FAIL(without): --no-ci-expected reached the guard without being requested" >&2
|
||||||
|
cat "$CALL_LOG" >&2
|
||||||
|
fail=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$fail" -eq 0 ]]; then
|
||||||
|
echo "pr-merge no-ci-expected pass-through regression passed"
|
||||||
|
fi
|
||||||
|
exit "$fail"
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
"lint": "eslint src",
|
"lint": "eslint src",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"test": "vitest run --passWithNoTests && pnpm run test:framework-shell",
|
"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 framework/tools/quality/scripts/test-framework-drift-check.py && bash framework/tools/quality/scripts/test-framework-drift-doctor.sh && bash framework/systemd/user/test-fleet-units.sh && python3 src/lease-broker/daemon_deadline_unittest.py && python3 src/lease-broker/normative_fragments_unittest.py && python3 src/lease-broker/promotion_binding_unittest.py && python3 src/lease-broker/promotion_trigger_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/receipt_observer_client_unittest.py && python3 src/lease-broker/invariant_r_unittest.py && python3 src/lease-broker/framework_skill_portability_unittest.py && python3 src/lease-broker/revoke_noop_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-edit.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-no-status.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-fork-ci-status.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/_scripts/test-mosaic-init-rce.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 && bash framework/tools/glpi/test-list-http-status.sh && bash framework/tools/orchestrator/test-board-roll.sh && bash framework/tools/woodpecker/test-ci-wait-exit-matrix.sh && bash framework/tools/_scripts/test-fleet-transport-check.sh && bash framework/tools/_scripts/test-brain-home-check.sh && bash framework/tools/fleet/test-agent-session-broker-preflight.sh"
|
"test:framework-shell": "bash framework/tools/quality/scripts/check-test-enumeration.sh && bash framework/tools/quality/scripts/test-check-test-enumeration.sh && python3 framework/tools/quality/scripts/test-framework-drift-check.py && bash framework/tools/quality/scripts/test-framework-drift-doctor.sh && bash framework/systemd/user/test-fleet-units.sh && python3 src/lease-broker/daemon_deadline_unittest.py && python3 src/lease-broker/normative_fragments_unittest.py && python3 src/lease-broker/promotion_binding_unittest.py && python3 src/lease-broker/promotion_trigger_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/receipt_observer_client_unittest.py && python3 src/lease-broker/invariant_r_unittest.py && python3 src/lease-broker/framework_skill_portability_unittest.py && python3 src/lease-broker/revoke_noop_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-edit.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-no-status.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-ci-queue-wait-no-ci-expected.sh && bash framework/tools/git/test-pr-merge-queue-branch.sh && bash framework/tools/git/test-pr-merge-no-ci-expected.sh && bash framework/tools/git/test-pr-merge-fork-ci-status.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/_scripts/test-mosaic-init-rce.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 && bash framework/tools/glpi/test-list-http-status.sh && bash framework/tools/orchestrator/test-board-roll.sh && bash framework/tools/woodpecker/test-ci-wait-exit-matrix.sh && bash framework/tools/_scripts/test-fleet-transport-check.sh && bash framework/tools/_scripts/test-brain-home-check.sh && bash framework/tools/fleet/test-agent-session-broker-preflight.sh"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mosaicstack/brain": "workspace:*",
|
"@mosaicstack/brain": "workspace:*",
|
||||||
|
|||||||
Reference in New Issue
Block a user