Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 29 additions & 9 deletions .github/scripts/governance-status.sh
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
#!/usr/bin/env bash
# governance-status.sh — read-only default-branch governance sensor
# (ADR-0004 decisions 1, 2, 4, 5). Compares effective branch rules, Actions
# posture, CODEOWNERS tuning, and merge-queue applicability against an
# explicitly declared solo or team intent (solo = the setup-ruleset.sh
# minimum; stronger observed settings never make solo unhealthy). Aggregates
# every active rule source including parent rulesets, qualifies
# ruleset-derived controls with their bypass actors, and reports missing
# evidence as UNKNOWN or UNCHECKABLE — never as safe. GET-only; nothing is
# mutated and no profile is persisted.
# posture, CODEOWNERS tuning, and merge-queue applicability against a solo
# or team intent (solo = the setup-ruleset.sh minimum; stronger observed
# settings never make solo unhealthy). When --profile is omitted, intent is
# read from the persisted repository Actions variable
# SCAFFOLD_GOVERNANCE_PROFILE (written by setup-ruleset.sh); only the exact
# value solo or team is accepted, and explicit --profile is a one-shot
# override that never reads that variable. Aggregates every active rule
# source including parent rulesets, qualifies ruleset-derived controls with
# their bypass actors, and reports missing evidence as UNKNOWN or
# UNCHECKABLE — never as safe. GET-only; nothing is mutated and no profile
# is persisted.
#
# Output: deterministic `key<TAB>state<TAB>detail` lines.
# Exit: 0 healthy with complete evidence; 1 required control OFF;
Expand Down Expand Up @@ -43,8 +47,6 @@ trap 'rm -rf "$WORK"' EXIT
TAB="$(printf '\t')"

BASE=0 TEAM=0 UNMET=0 MISSING=0
[ -z "$PROFILE" ] || BASE=1
[ "$PROFILE" != team ] || TEAM=1

# fetch <name> <path> [raw|page] — read-only GET; records ok|404|fail in
# $WORK/<name>.rc. The only gh invocation shapes this sensor ever uses.
Expand All @@ -66,6 +68,24 @@ fetch() {
st() { cat "$WORK/$1.rc" 2>/dev/null || echo fail; }
jqr() { jq -r "$1" "$WORK/$2.json"; }

# Persisted governance intent: consulted only when no explicit --profile was
# given, so an override never reads this endpoint. Only the exact value
# solo or team is accepted — never trimmed, never case-folded — so a
# missing variable, an Actions-disabled target, an API/authorization
# failure, a malformed response, an empty value, a whitespace or case
# variant, or any other value all leave PROFILE unset, which the existing
# UNKNOWN/exit-3 path below already reports faithfully; no default is
# guessed.
if [ -z "$PROFILE" ]; then
fetch govvar "repos/$REPO/actions/variables/SCAFFOLD_GOVERNANCE_PROFILE"
if [ "$(st govvar)" = ok ]; then
GV="$(jqr '.value // empty' govvar)"
case "$GV" in solo|team) PROFILE="$GV" ;; esac
fi
fi
[ -z "$PROFILE" ] || BASE=1
[ "$PROFILE" != team ] || TEAM=1

# emit <key> <state> <detail> <required-flag> — required OFF counts toward
# exit 1; required UNKNOWN/UNCHECKABLE counts toward exit 3 (which outranks).
emit() {
Expand Down
69 changes: 69 additions & 0 deletions .github/scripts/tests/test-governance-status.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ case "$path" in
repos/*/rulesets/*) f="rs-repo-${path##*/}.json" ;;
orgs/*/rulesets/*) f="rs-org-${path##*/}.json" ;;
orgs/*) f=org.json ;;
repos/*/actions/variables/SCAFFOLD_GOVERNANCE_PROFILE) f=govvar.json ;;
repos/*/actions/permissions/workflow) f=workflow.json ;;
repos/*/commits/*/check-runs*) f=checkruns.json ;;
repos/*/contents/.github/workflows/*) p="${path%%\?*}"; f="wff-${p##*/}" ;;
Expand Down Expand Up @@ -103,6 +104,7 @@ mk_marker() { printf '# log\n<!-- scaffold-version: repo=o/r sha=%s date=x -->\n
mk_co() { printf '%s\n/.github/docs/agreements/ @owner\n' "$1" > "$GS_FIX/codeowners.raw"; }
mk_wfdir() { local out="" sep="" n; for n in "$@"; do out="$out$sep{\"name\":\"$n\",\"type\":\"file\"}"; sep=","; done; printf '[%s]\n' "$out" > "$GS_FIX/wfdir.json"; }
mk_wff() { printf '%s\n' "$2" > "$GS_FIX/wff-$1"; }
mk_govvar() { printf '{"name":"SCAFFOLD_GOVERNANCE_PROFILE","value":"%s"}\n' "$1" > "$GS_FIX/govvar.json"; }

RRB='[{"actor_id":5,"actor_type":"RepositoryRole","bypass_mode":"pull_request"}]'
baseline() { # live-like template repository on the solo minimum
Expand Down Expand Up @@ -149,7 +151,74 @@ if [ "$first" = "$out" ]; then t_ok "output is deterministic across runs"; else
run -R o/r
rce "omitted profile exits 3, never guessed" 3
chk "omitted profile reported UNKNOWN" "^governance\.profile${T}UNKNOWN"
if grep -q 'api repos/o/r/actions/variables/SCAFFOLD_GOVERNANCE_PROFILE' "$GH_CALLS"; then t_ok "omitted profile is read from the persisted variable endpoint"; else t_fail "omitted profile is read from the persisted variable endpoint"; fi

# Persisted governance intent (#101): with no --profile, the sensor reads
# SCAFFOLD_GOVERNANCE_PROFILE and accepts only the exact value solo or team.
baseline
mk_govvar solo
run -R o/r
rce "persisted solo intent drives the same healthy report as explicit solo" 0
chk "persisted solo profile is ACTIVE" "^governance\.profile${T}ACTIVE${T}solo$"

team_green
mk_govvar team
run -R o/r
rce "persisted team intent drives the same healthy report as explicit team" 0
chk "persisted team profile is ACTIVE" "^governance\.profile${T}ACTIVE${T}team$"

baseline
mk_govvar team
run -R o/r
rce "persisted team intent still gates an unhardened repository" 1
chk "persisted team gap: stale reviews OFF" "^pull_request\.dismiss_stale_reviews${T}OFF${T}false"

baseline
rm -f "$GS_FIX/govvar.json"
run -R o/r
rce "missing persisted variable exits 3, never guessed" 3
chk "missing persisted variable reported UNKNOWN" "^governance\.profile${T}UNKNOWN"

for bad in '' ' solo' 'solo ' 'Solo' 'TEAM' 'nonsense'; do
baseline
mk_govvar "$bad"
run -R o/r
rce "invalid persisted value '$bad' exits 3, never guessed" 3
chk "invalid persisted value '$bad' reported UNKNOWN" "^governance\.profile${T}UNKNOWN"
done

baseline
printf '{"name":"SCAFFOLD_GOVERNANCE_PROFILE"}\n' > "$GS_FIX/govvar.json"
run -R o/r
rce "persisted payload without a value field exits 3" 3
chk "absent value field reported UNKNOWN" "^governance\.profile${T}UNKNOWN"

baseline
printf 'not json\n' > "$GS_FIX/govvar.json"
run -R o/r
rce "malformed (non-JSON) persisted response exits 3" 3
chk "malformed persisted response reported UNKNOWN" "^governance\.profile${T}UNKNOWN"

baseline
mk_govvar solo
runf "actions/variables" -R o/r
rce "persisted variable read failure (Actions-disabled or unauthorized) exits 3" 3
chk "failed persisted read reported UNKNOWN" "^governance\.profile${T}UNKNOWN"

baseline
mk_govvar solo
GH_CALLS_MARK="$(wc -l < "$GH_CALLS")"
run -R o/r --profile team
rce "explicit --profile overrides persisted intent" 1
chk "explicit override drives team requirements, not the persisted solo value" "^pull_request\.dismiss_stale_reviews${T}OFF${T}false"
NEWCALLS="$(tail -n "+$((GH_CALLS_MARK + 1))" "$GH_CALLS")"
if printf '%s\n' "$NEWCALLS" | grep -q 'actions/variables/SCAFFOLD_GOVERNANCE_PROFILE'; then
t_fail "explicit --profile must not read the persisted variable endpoint"
else
t_ok "explicit --profile never reads the persisted variable endpoint"
fi

baseline
run -R o/r --profile team
rce "solo baseline fails team intent" 1
chk "team gap: stale reviews OFF" "^pull_request\.dismiss_stale_reviews${T}OFF${T}false"
Expand Down
Loading