#!/bin/bash
# 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] [--no-ci-expected]

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/detect-platform.sh"

BRANCH=""
TARGET_REPO=""
HEAD_SHA=""
TIMEOUT_SEC=900
INTERVAL_SEC=15
PURPOSE="merge"
REQUIRE_STATUS=0
NO_CI_EXPECTED=0

usage() {
    cat <<EOF
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:
  -B, --branch BRANCH      Branch head to inspect (default: current branch)
  -R, --repo OWNER/REPO    Repository containing the branch (default: origin repo)
      --sha FULL_SHA       Inspect this exact 40-character commit instead of resolving the branch
  -t, --timeout SECONDS    Max wait time in seconds (default: 900)
  -i, --interval SECONDS   Poll interval in seconds (default: 15)
      --purpose VALUE      Log context: push|merge (default: merge)
      --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

Examples:
  $(basename "$0")
  $(basename "$0") --purpose push -t 600 -i 10
EOF
}

# get_remote_host and get_gitea_token are provided by detect-platform.sh

get_state_from_status_json() {
    # Python source comes from -c so the provider payload remains on stdin.
    # Never move the payload to argv: commit-status responses can exceed ARG_MAX.
    python3 -c '
import json
import sys

try:
    payload = json.load(sys.stdin)
    if not isinstance(payload, dict):
        raise ValueError("status payload is not an object")
except Exception:
    print("malformed")
    raise SystemExit(0)

# Gitea returns "statuses": null (not []) for a commit with zero status
# contexts -- e.g. any repo with no CI configured. Treat null as empty.
raw_statuses = payload.get("statuses", [])
if raw_statuses is None:
    raw_statuses = []
raw_state = payload.get("state", "")
if not isinstance(raw_statuses, list) or not isinstance(raw_state, str):
    print("malformed")
    raise SystemExit(0)
statuses = raw_statuses
state = raw_state.lower()

pending_values = {"pending", "queued", "running", "waiting"}
failure_values = {"failure", "error", "failed"}
success_values = {"success"}

values = []
for item in statuses:
    if not isinstance(item, dict):
        print("malformed")
        raise SystemExit(0)
    raw_value = item.get("status") or item.get("state")
    if not isinstance(raw_value, str) or not raw_value:
        print("malformed")
        raise SystemExit(0)
    values.append(raw_value.lower())

# Zero contexts is classified FIRST: Gitea reports a synthetic aggregate
# state of "pending" alongside total_count:0, and an aggregate with no
# contexts behind it must not read as an in-flight pipeline (it would poll
# to the timeout). With zero contexts there is nothing to wait on.
if not values:
    print("no-status")
elif any(value in pending_values for value in values) or state in pending_values:
    print("pending")
elif any(value in failure_values for value in values) or state in failure_values:
    print("terminal-failure")
elif all(value in success_values for value in values) and state in {"", "success"}:
    print("terminal-success")
else:
    print("unknown")
'
}

print_pending_contexts() {
    python3 -c '
import json
import sys

try:
    payload = json.load(sys.stdin)
except Exception:
    print("[ci-queue-wait] unable to decode status payload")
    raise SystemExit(0)

statuses = payload.get("statuses") or []
if not statuses:
    print("[ci-queue-wait] no status contexts reported")
    raise SystemExit(0)

pending_values = {"pending", "queued", "running", "waiting"}
found = False
for item in statuses:
    if not isinstance(item, dict):
        continue
    name = item.get("context") or item.get("name") or "unknown-context"
    value = str(item.get("status") or item.get("state") or "unknown").lower()
    target = item.get("target_url") or item.get("url") or ""
    if value in pending_values:
        found = True
        suffix = f" ({target})" if target else ""
        print(f"[ci-queue-wait] pending: {name}={value}{suffix}")
if not found:
    print("[ci-queue-wait] no pending contexts")
'
}

record_cannot_assert() {
    local reason="$1"
    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: CANNOT_ASSERT and audit directory is unavailable; refusing degraded pass." >&2
        return 70
    fi

    if ! python3 - "$audit_log" "$reason" "${PLATFORM:-unknown}" "$PURPOSE" "${BRANCH:-unknown}" "${OWNER:-unknown}/${REPO:-unknown}" <<'PY'
import datetime
import json
import os
import sys

path, reason, platform, purpose, branch, repo = sys.argv[1:]
record = {
    "timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
    "outcome": "CANNOT_ASSERT",
    "reason": reason,
    "platform": platform,
    "purpose": purpose,
    "disposition": "hold" if purpose == "merge" else "degraded-pass",
    "branch": branch,
    "repo": repo,
}
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: CANNOT_ASSERT and audit write failed at ${audit_log}; refusing degraded pass." >&2
        return 70
    fi

    if [[ "$PURPOSE" == "merge" ]]; then
        echo "[ci-queue-wait] CANNOT_ASSERT reason=${reason} purpose=merge branch=${BRANCH:-unknown}; audited=${audit_log}; HOLD (exit 75). Retry after provider recovery; no manual reset is required." >&2
        return 75
    fi

    echo "[ci-queue-wait] CANNOT_ASSERT reason=${reason} purpose=push branch=${BRANCH:-unknown}; audited=${audit_log}; push may proceed in degraded mode." >&2
    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() {
    local owner="$1"
    local repo="$2"
    local branch="$3"
    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() {
    local owner="$1"
    local repo="$2"
    local sha="$3"
    local work_root status_file checks_file
    work_root="${AGENT_WORK_ROOT:-${HOME:-}/.cache/mosaic/ci-queue-wait}"
    mkdir -p "$work_root" || return 1
    status_file=$(mktemp "$work_root/github-status.XXXXXX") || return 1
    checks_file=$(mktemp "$work_root/github-checks.XXXXXX") || {
        rm -f "$status_file"
        return 1
    }

    if ! gh api --paginate --slurp "repos/${owner}/${repo}/commits/${sha}/statuses?per_page=100" > "$status_file" ||
       ! gh api --paginate --slurp "repos/${owner}/${repo}/commits/${sha}/check-runs?per_page=100&filter=latest" > "$checks_file"; then
        rm -f "$status_file" "$checks_file"
        return 1
    fi

    python3 - "$status_file" "$checks_file" <<'PY'
import json
import sys

with open(sys.argv[1], encoding="utf-8") as handle:
    status_pages = json.load(handle)
with open(sys.argv[2], encoding="utf-8") as handle:
    check_pages = json.load(handle)

if not isinstance(status_pages, list) or not isinstance(check_pages, list):
    raise SystemExit(1)

# The statuses endpoint is newest-first and can contain retries for one context.
# Keep only the newest entry per context after flattening every page.
combined = []
seen_contexts = set()
for page in status_pages:
    if not isinstance(page, list):
        raise SystemExit(1)
    for status in page:
        if not isinstance(status, dict):
            raise SystemExit(1)
        context = status.get("context")
        if not isinstance(context, str) or not context or context in seen_contexts:
            continue
        seen_contexts.add(context)
        combined.append(status)

check_runs = []
reported_total = 0
for page in check_pages:
    if not isinstance(page, dict):
        raise SystemExit(1)
    page_runs = page.get("check_runs") or []
    total_count = page.get("total_count")
    if not isinstance(page_runs, list) or not isinstance(total_count, int):
        raise SystemExit(1)
    reported_total = max(reported_total, total_count)
    check_runs.extend(page_runs)
if len(check_runs) < reported_total:
    raise SystemExit(1)

for run in check_runs:
    if not isinstance(run, dict):
        raise SystemExit(1)
    status = run.get("status")
    conclusion = run.get("conclusion")
    if status != "completed":
        value = "pending"
    elif conclusion == "success":
        value = "success"
    elif conclusion in {"failure", "cancelled", "timed_out", "action_required", "startup_failure", "stale"}:
        value = "failure"
    else:
        value = "unknown"
    combined.append({
        "context": run.get("name") or "github-check",
        "status": value,
        "target_url": run.get("html_url") or run.get("details_url") or "",
    })

json.dump({"state": "", "statuses": combined}, sys.stdout)
PY
    local status=$?
    rm -f "$status_file" "$checks_file"
    return "$status"
}

gitea_get_branch_head_sha() {
    local host="$1"
    local repo="$2"
    local branch="$3"
    local token="$4"
    local url="https://${host}/api/v1/repos/${repo}/branches/${branch}"
    # Capture HTTP status so an absent branch (404) is distinguished from an API
    # error. A not-yet-pushed feature branch has no in-flight pipeline, so the
    # pre-push queue guard must treat 404 as "queue clear", not crash.
    local resp code body
    resp=$(curl -sS -H "User-Agent: curl/8" -H "Authorization: token ${token}" -w $'\n%{http_code}' "$url")
    code="${resp##*$'\n'}"
    body="${resp%$'\n'*}"
    if [[ "$code" == "404" ]]; then
        echo "__BRANCH_ABSENT__"
        return 0
    fi
    if [[ "$code" != "200" ]]; then
        return 1
    fi
    printf '%s' "$body" | python3 -c '
import json, sys
data = json.load(sys.stdin)
commit = data.get("commit") or {}
print((commit.get("id") or "").strip())
'
}

gitea_get_commit_status_json() {
    local host="$1"
    local repo="$2"
    local sha="$3"
    local token="$4"
    local url="https://${host}/api/v1/repos/${repo}/commits/${sha}/status"
    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
    case "$1" in
        -B|--branch)
            BRANCH="$2"
            shift 2
            ;;
        -R|--repo)
            TARGET_REPO="$2"
            shift 2
            ;;
        --sha)
            HEAD_SHA="$2"
            shift 2
            ;;
        -t|--timeout)
            TIMEOUT_SEC="$2"
            shift 2
            ;;
        -i|--interval)
            INTERVAL_SEC="$2"
            shift 2
            ;;
        --purpose)
            PURPOSE="$2"
            shift 2
            ;;
        --require-status)
            REQUIRE_STATUS=1
            shift
            ;;
        --no-ci-expected)
            NO_CI_EXPECTED=1
            shift
            ;;
        -h|--help)
            usage
            exit 0
            ;;
        *)
            echo "Unknown option: $1" >&2
            usage >&2
            exit 1
            ;;
    esac
done

if ! [[ "$TIMEOUT_SEC" =~ ^[0-9]+$ ]] || ! [[ "$INTERVAL_SEC" =~ ^[0-9]+$ ]]; then
    echo "Error: timeout and interval must be integer seconds." >&2
    exit 1
fi
if [[ -n "$HEAD_SHA" && ! "$HEAD_SHA" =~ ^[0-9a-fA-F]{40}$ ]]; then
    echo "Error: --sha must be a full 40-character hexadecimal commit SHA." >&2
    exit 1
fi
if [[ -n "$TARGET_REPO" && ! "$TARGET_REPO" =~ ^[^/[:space:]]+/[^/[:space:]]+$ ]]; then
    echo "Error: --repo must be OWNER/REPO." >&2
    exit 1
fi

if [[ "$PURPOSE" != "push" && "$PURPOSE" != "merge" ]]; then
    echo "Error: --purpose must be push or merge." >&2
    exit 1
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"
REPO="unknown"
PLATFORM="unknown"
if ! OWNER=$(get_repo_owner) || [[ -z "$OWNER" ]]; then
    record_cannot_assert "repository-owner-unresolvable"
    exit $?
fi
if ! REPO=$(get_repo_name) || [[ -z "$REPO" ]]; then
    record_cannot_assert "repository-name-unresolvable"
    exit $?
fi
if ! detect_platform > /dev/null; then
    PLATFORM="${PLATFORM:-unknown}"
    record_cannot_assert "unsupported-platform"
    exit $?
fi
PLATFORM="${PLATFORM:-unknown}"

if [[ -n "$TARGET_REPO" ]]; then
    OWNER="${TARGET_REPO%%/*}"
    REPO="${TARGET_REPO##*/}"
fi

if [[ -z "$BRANCH" ]]; then
    if ! BRANCH=$(git symbolic-ref --quiet --short HEAD) || [[ -z "$BRANCH" ]]; then
        record_cannot_assert "current-branch-unresolvable"
        exit $?
    fi
fi

# T51 WP5b (spec 4.1, review ruling C4/F8): the declaration adds ROUTE CONTEXT only.
# Branch-selection semantics are UNCHANGED — the guard keeps inspecting the
# exact head above. No declaration dependency gates the wait (4.3/DR2 R9:
# blocking here adds a blocker with no safety gain); absence is silent.
# shellcheck source=packages/mosaic/framework/tools/git/ci-queue-wait.sh
if git rev-parse --show-toplevel >/dev/null 2>&1 \
   && [ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/repo-decl.sh" ]; then
    source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/repo-decl.sh"
    repo_decl_load
    if [[ "$DECL_STATE" == invalid ]]; then
        repo_decl_report_invalid
    elif [[ "$DECL_STATE" == valid && "$DECL_SCHEMA" == 2 ]]; then
        route="feature"
        if [[ "$BRANCH" == "$DECL_TRUNK" ]]; then
            route="trunk (integration head)"
        elif [[ "$BRANCH" == "$DECL_RELEASE" ]]; then
            route="release branch"
        fi
        echo "repo-decl: route context flow=$DECL_FLOW trunk=$DECL_TRUNK release=$DECL_RELEASE; guarded head '$BRANCH' is a $route head (spec 4.1)" >&2
    fi
fi

if [[ "$PLATFORM" == "github" ]]; then
    if ! command -v gh >/dev/null 2>&1; then
        record_cannot_assert "github-cli-unavailable"
        exit $?
    fi
    if [[ -z "$HEAD_SHA" ]]; then
        if ! HEAD_SHA=$(github_get_branch_head_sha "$OWNER" "$REPO" "$BRANCH") || [[ -z "$HEAD_SHA" ]]; then
            record_cannot_assert "branch-head-unavailable"
            exit $?
        fi
    fi
    echo "[ci-queue-wait] platform=github purpose=${PURPOSE} branch=${BRANCH} sha=${HEAD_SHA}"
elif [[ "$PLATFORM" == "gitea" ]]; then
    if ! HOST=$(get_remote_host) || [[ -z "$HOST" ]]; then
        record_cannot_assert "remote-host-unresolvable"
        exit $?
    fi
    if ! TOKEN=$(get_gitea_token "$HOST") || [[ -z "$TOKEN" ]]; then
        record_cannot_assert "credential-unresolvable"
        exit $?
    fi
    if [[ -z "$HEAD_SHA" ]]; then
        if ! HEAD_SHA=$(gitea_get_branch_head_sha "$HOST" "$OWNER/$REPO" "$BRANCH" "$TOKEN"); then
            record_cannot_assert "branch-head-unavailable"
            exit $?
        fi
        if [[ "$HEAD_SHA" == "__BRANCH_ABSENT__" ]]; then
            echo "[ci-queue-wait] branch ${BRANCH} not yet on remote — no in-flight pipeline; queue clear."
            exit 0
        fi
        if [[ -z "$HEAD_SHA" ]]; then
            record_cannot_assert "branch-head-unavailable"
            exit $?
        fi
    fi
    echo "[ci-queue-wait] platform=gitea purpose=${PURPOSE} branch=${BRANCH} sha=${HEAD_SHA}"
else
    record_cannot_assert "unsupported-platform"
    exit $?
fi

START_TS=$(date +%s)
DEADLINE_TS=$((START_TS + TIMEOUT_SEC))

while true; do
    NOW_TS=$(date +%s)
    if (( NOW_TS > DEADLINE_TS )); then
        echo "Error: ASSERTED_NOT_READY state=pending; timed out waiting for CI queue to clear on ${BRANCH} after ${TIMEOUT_SEC}s." >&2
        exit 124
    fi

    if [[ "$PLATFORM" == "github" ]]; then
        if ! STATUS_JSON=$(github_get_commit_status_json "$OWNER" "$REPO" "$HEAD_SHA"); then
            record_cannot_assert "status-provider-unreachable"
            exit $?
        fi
    else
        if ! STATUS_JSON=$(gitea_get_commit_status_json "$HOST" "$OWNER/$REPO" "$HEAD_SHA" "$TOKEN"); then
            record_cannot_assert "status-provider-unreachable"
            exit $?
        fi
    fi

    STATE=$(printf '%s' "$STATUS_JSON" | get_state_from_status_json)
    echo "[ci-queue-wait] state=${STATE} purpose=${PURPOSE} branch=${BRANCH}"

    case "$STATE" in
        pending)
            printf '%s' "$STATUS_JSON" | print_pending_contexts
            sleep "$INTERVAL_SEC"
            ;;
        terminal-success)
            exit 0
            ;;
        no-status)
            if [[ "$REQUIRE_STATUS" -eq 1 ]]; then
                echo "Error: ASSERTED_NOT_READY state=no-status; --require-status was set for ${BRANCH}." >&2
                exit 3
            fi
            # A head with zero status contexts has no CI queue to wait on.
            # For push, that is queue-clear (a repo with no CI must remain
            # pushable) -- mirroring record_cannot_assert's dispositions
            # (push=degraded-pass, merge=hold). Merge stays fail-closed:
            # no-status there may just mean CI has not reported yet.
            if [[ "$PURPOSE" == "push" ]]; then
                echo "[ci-queue-wait] queue-clear state=no-status purpose=push branch=${BRANCH}; no queued or running CI."
                exit 0
            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
            exit 3
            ;;
        terminal-failure)
            if [[ "$PURPOSE" == "push" ]]; then
                echo "[ci-queue-wait] queue-clear state=terminal-failure purpose=push branch=${BRANCH}; no queued or running CI."
                exit 0
            fi
            echo "Error: ASSERTED_NOT_READY state=terminal-failure purpose=${PURPOSE} branch=${BRANCH}." >&2
            exit 3
            ;;
        malformed|unknown)
            echo "Error: ASSERTED_NOT_READY state=${STATE} purpose=${PURPOSE} branch=${BRANCH}." >&2
            exit 3
            ;;
        *)
            echo "Error: ASSERTED_NOT_READY unrecognized-state=${STATE} purpose=${PURPOSE} branch=${BRANCH}." >&2
            exit 3
            ;;
    esac
done
