diff --git a/scripts/ci-health/README.adoc b/scripts/ci-health/README.adoc index 39b46a8b..8d6db9d2 100644 --- a/scripts/ci-health/README.adoc +++ b/scripts/ci-health/README.adoc @@ -28,10 +28,14 @@ uploaded because the job never starts). | **Report only** — owner clears it in GitHub → Settings → Billing & plans. | `B-ALLOWLIST` -| `allowed_actions: selected` with `patterns_allowed` missing `hyperpolymath/*`. -Only GitHub-owned + verified-marketplace actions resolve, so every -`hyperpolymath/*` reusable + every non-verified third-party action is blocked -at parse → `startup_failure` (zero jobs, no logs). Hits public repos too. +| `allowed_actions: selected` with `patterns_allowed` that does **not cover the +curated superset** — either empty (post-wipe: `hyperpolymath/*` absent) or +incomplete (has `hyperpolymath/*` but missing a third-party action, e.g. +`gitleaks`). Only GitHub-owned + verified-marketplace actions resolve, so every +uncovered `hyperpolymath/*` reusable + non-verified third-party action is blocked +at parse → `startup_failure` (zero jobs, no logs). Hits public repos too. The +required set mirrors `remediate.sh`'s PUT body exactly, so a fix converges to +zero-missing and this stops re-firing (idempotent). | **Auto-fix** — PUT the curated superset (`action-superset.txt`), keep `sha_pinning_required`. diff --git a/scripts/ci-health/detect.sh b/scripts/ci-health/detect.sh index d359eb23..4471cde3 100755 --- a/scripts/ci-health/detect.sh +++ b/scripts/ci-health/detect.sh @@ -19,6 +19,7 @@ # banner does (it names the blocked action). That is the human diagnostic. set -euo pipefail O="${OWNER:-hyperpolymath}"; R="$1" +HERE="$(cd "$(dirname "$0")" && pwd)" # for action-superset.txt (allow-list coverage) emit(){ printf '%s\t%s\t%s\t%s\n' "$R" "$1" "$2" "$3"; } # --- A: billing wall (a failure run whose job annotation matches the signature) @@ -33,12 +34,24 @@ if [ -n "${fail_id:-}" ]; then fi fi -# --- B: allow-list misconfig (the root cause of estate startup_failure) +# --- B: allow-list under-coverage (the root cause of estate startup_failure). +# Fire when selected-mode and the allow-list does NOT cover the full curated +# superset that remediate.sh PUTs. Catches BOTH an empty list (post-wipe: +# hyperpolymath/* absent) AND an incomplete one (has hyperpolymath/* but is +# missing a third-party action, e.g. gitleaks — previously only B-STARTUPFAIL, +# which has no remediation). The required set mirrors remediate.sh's PUT body +# exactly (hyperpolymath/* + each superset line as owner/repo@*), so a remediate +# converges this to zero-missing and detect stops re-firing (idempotent). aa=$(gh api "repos/$O/$R/actions/permissions" --jq '.allowed_actions // empty' 2>/dev/null || true) if [ "$aa" = "selected" ]; then - has=$(gh api "repos/$O/$R/actions/permissions/selected-actions" --jq '(.patterns_allowed // [])|index("hyperpolymath/*") // "MISSING"' 2>/dev/null || echo MISSING) - if [ "$has" = "MISSING" ]; then - emit B-ALLOWLIST HIGH "selected + no hyperpolymath/* pattern → reusables & non-verified actions startup_failure → apply curated superset" + cur=$(gh api "repos/$O/$R/actions/permissions/selected-actions" --jq '(.patterns_allowed // [])[]' 2>/dev/null || true) + req=$(printf 'hyperpolymath/*\n'; sed 's/^[[:space:]]*//;s/[[:space:]]*$//;/^$/d;s/$/@*/' "$HERE/action-superset.txt") + miss=$(printf '%s\n' "$req" | grep -vxF -f <(printf '%s\n' "$cur" | grep .) - || true) + if [ -n "$miss" ]; then + ntot=$(printf '%s\n' "$req" | grep -c .) + nmiss=$(printf '%s\n' "$miss" | grep -c .) + first=$(printf '%s\n' "$miss" | head -n1) + emit B-ALLOWLIST HIGH "selected + allow-list missing $nmiss/$ntot curated pattern(s) (e.g. $first) → apply curated superset" fi fi