shoc-backend/scripts/governance-check.sh

132 lines
4.8 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
#
# governance-check.sh — local/CI parity governance gate for the Seahaven backend.
#
# Locally this runs G1–G5, G10, G11, promotion-script tests, and live G13.
# The architecture job in ci.yml sets GOVERNANCE_SKIP_BUILD_TEST=1 so G4/G5
# run only in the Build and test job. Live G13 runs on pull_request and local
# invocations; it skips merge_group and push.
#
# Usage:
# bash scripts/governance-check.sh
# BASE_REF=origin/main bash scripts/governance-check.sh
# BASE_REF=<base-sha> HEAD_REF=<head-sha> bash scripts/governance-check.sh
set -euo pipefail
SOLUTION="SeaHavenIndustries.sln"
ARCH_TEST_PROJECT="Api.SeaHavenIndustries.Tests/Api.SeaHavenIndustries.Tests.csproj"
LIVE_ROOTS=(terraform/live/dev terraform/live/staging)
log() { printf '\n\033[1m== %s ==\033[0m\n' "$1"; }
ok() { printf '\033[32mPASS\033[0m %s\n' "$1"; }
bad() { printf '\033[31mFAIL\033[0m %s\n' "$1"; }
if [[ -n "${DOTNET_BIN:-}" ]]; then
DOTNET="$DOTNET_BIN"
elif command -v dotnet >/dev/null 2>&1; then
DOTNET="$(command -v dotnet)"
elif [[ -x "$HOME/.dotnet/dotnet" ]]; then
DOTNET="$HOME/.dotnet/dotnet"
else
bad "dotnet is unavailable; set DOTNET_BIN or install the repository SDK."
exit 1
fi
# Comparison point for changed-file formatting. Default to main locally; CI
# overrides BASE_REF/HEAD_REF with the PR base/head SHAs.
BASE_REF="${BASE_REF:-origin/main}"
HEAD_REF="${HEAD_REF:-HEAD}"
# Resolve the base ref before using it for a diff.
if ! git rev-parse --verify --quiet "${BASE_REF}^{commit}" >/dev/null; then
bad "G3: BASE_REF '${BASE_REF}' does not resolve to a commit (run: git fetch origin)."
exit 1
fi
# Diff the change set from the merge base, not from the base tip. A two-dot
# diff against a moving base reports everything the base gained after the
# branch point as if this change reverted it, so a branch behind main that
# touches C# would fail G13 whenever main had merged Terraform in the meantime.
# The merge queue no longer requires branches to be current, so this matters.
DIFF_BASE="$(git merge-base "${BASE_REF}" "${HEAD_REF}")" || {
bad "G3: no merge base between '${BASE_REF}' and '${HEAD_REF}'."
exit 1
}
log "G1: restore"
"$DOTNET" restore "$SOLUTION"
ok "G1: restore"
log "G2: architecture boundary tests (dependency direction)"
"$DOTNET" test "$ARCH_TEST_PROJECT" \
--no-restore \
--filter "FullyQualifiedName~ArchitectureTests" \
--nologo
ok "G2: ArchitectureTests"
log "G3: changed-file formatting (${BASE_REF}...${HEAD_REF}, merge base ${DIFF_BASE:0:7})"
changed_cs=()
while IFS= read -r f; do
changed_cs+=("$f")
done < <(
git diff --name-only --diff-filter=ACMR "${DIFF_BASE}" "${HEAD_REF}" -- '*.cs'
)
if (( ${#changed_cs[@]} == 0 )); then
printf '\033[33mSKIP\033[0m G3: no changed C# files between %s...%s\n' "${BASE_REF}" "${HEAD_REF}"
else
printf ' checking %d changed C# file(s)\n' "${#changed_cs[@]}"
"$DOTNET" format "$SOLUTION" \
--no-restore \
--verify-no-changes \
--include "${changed_cs[@]}"
ok "G3: changed-file formatting"
fi
if [[ "${GOVERNANCE_SKIP_BUILD_TEST:-}" == "1" ]]; then
printf '\033[33mSKIP\033[0m G4: Release build (CI Build and test job owns it)\n'
printf '\033[33mSKIP\033[0m G5: full test suite (CI Build and test job owns it)\n'
else
log "G4: Release build"
"$DOTNET" build "$SOLUTION" -c Release --no-restore --nologo
ok "G4: Release build"
log "G5: full test suite"
"$DOTNET" test "$SOLUTION" -c Release --no-build --nologo
ok "G5: full test suite"
fi
log "G10: Terraform import plan safety"
python scripts/test-terraform-import-plan-check.py
ok "G10: Terraform import plan safety"
log "Release promotion scripts"
python3 scripts/test_next_release_tag.py
python3 scripts/test_require_commit_checks.py
python3 scripts/test_check_app_terraform_isolation.py
ok "Release promotion scripts"
log "G11: Terraform formatting and validation"
if ! command -v terraform >/dev/null 2>&1; then
bad "G11: terraform is unavailable; install Terraform or set PATH."
exit 1
fi
terraform fmt -check -recursive terraform
for live_root in "${LIVE_ROOTS[@]}"; do
terraform -chdir="${live_root}" init -backend=false -input=false -lockfile=readonly -no-color
terraform -chdir="${live_root}" validate -no-color
done
ok "G11: Terraform formatting and validation"
if [[ -n "${GITHUB_EVENT_NAME:-}" && "${GITHUB_EVENT_NAME}" != "pull_request" ]]; then
printf '\033[33mSKIP\033[0m G13: live isolation is a pull-request property (event: %s)\n' "${GITHUB_EVENT_NAME}"
else
log "G13: application and Terraform isolation (${BASE_REF}...${HEAD_REF}, merge base ${DIFF_BASE:0:7})"
python3 scripts/check_app_terraform_isolation.py < <(
git diff --name-only --diff-filter=ACMR "${DIFF_BASE}" "${HEAD_REF}"
)
ok "G13: application and Terraform isolation"
fi
log "governance-check: all required repository gates passed"