Files
summercms/scripts/check-phase19.6.1.sh
Jakub Zych e62086084b chore(19.6.1-04): require CR-01 and WR-03 tests in the phase gate
The docker stage must refuse a tree that drops TestRolesCodeDeclaredCodeRefused or TestActionExtensionOutranked.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 13:36:12 +02:00

571 lines
22 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# check-phase19.6.1: fail-closed gate for Phase 19.6.1, the backend
# Administrators and Roles screens in cabana with their privilege rules, the
# cross-controller record action seam (D-14) and the SPA's field triggers and
# per-record permission locks (D-21).
#
# Stages:
# go go vet ./..., go test -short ./..., go -C examples/hello test
# ./..., then the Docker-free screen, seam, ceiling, trigger and
# record-lock tests by required name (-short).
# docker Docker host only: every Postgres test of plans 01 and 03 by
# required name (without -short), the SC-1 regression tests of
# the admin commands, auth, account and OpenAPI conformance, and
# go test ./modules/surf.
# admin npm --prefix admin run typecheck, the vitest run (the trigger,
# permission editor, form state, form view and preview view test
# files must pass), the browser run (record-actions-wrap must
# pass), scripts/check-admin-openapi.sh --check and
# scripts/check-admin-dist.sh.
# docs TestDocsTree, summer docs:build --check, and no framework
# source, README or docs page under modules/ or docs/, and no
# test file of this phase, names a consuming application.
# sc1 SC-1: no commit in PHASE1961_BASE..HEAD that touches a phase
# file also touches modules/cabana/commands.go,
# token_commands.go or account.go (the admin commands and My
# account stay as they were).
#
# A Go stage exits non-zero on a failing command, or on a go test -json run
# that fails, skips a test (a subtest included), matches zero tests, prints
# "no tests to run" or misses a required name. The vitest detector refuses a
# run whose summary is not all-passing, that reports a failed, skipped or
# todo test, or that has no passing line for a required file. --self-test
# proves the detectors on planted output and the docker NOT RUN rule with a
# docker that does not answer.
#
# --all runs self-test, go, admin, docs and sc1, then docker when a Docker
# daemon answers (NOT RUN docker otherwise, a failure with
# PHASE1961_REQUIRE_DOCKER=1).
#
# Environment overrides: PHASE1961_ROOT (this repository), PHASE1961_BASE
# (the commit before the phase; default the parent of the first commit that
# added modules/cabana/backend_screens.go), PHASE1961_REQUIRE_DOCKER.
set -euo pipefail
unset FORCE_COLOR
export NO_COLOR=1
ROOT="${PHASE1961_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
# ---------------------------------------------------------------------------
# Required test names. Each list runs with -run '^(a|b|...)$' and every name
# must pass; a name that skips or does not run fails the stage.
# go stage: Docker-free tests in modules/cabana (-short).
SHORT_TESTS=(
# Plan 01.
TestBackendScreensSmoke
# Plan 02.
TestFieldTriggerSmoke TestPermissionRecordLocksSmoke
# Plan 03: activation (D-01, D-04), the seam (D-14), the role ceiling
# (D-07), triggers and record locks (D-21).
TestBackendScreensActivation TestActionExtensionBoot TestRoleCeiling
TestFieldTriggerBoot TestPermissionRecordLocks
)
# docker stage: Postgres tests in modules/cabana.
DOCKER_TESTS=(
# Plan 01 smokes.
TestBackendScreensPostgresSmoke TestAdministratorsFormSmoke
TestAdministratorsLifecycleSmoke TestRolesSmoke
# Plan 03: one test per rule (D-03, D-05..D-11, D-19, D-20, Pitfall 1).
TestAdministratorsSuperuserFlag TestAdministratorsSuperuserHidden
TestAdministratorsSuperuserProjected TestAdministratorsPermissionCeiling
TestAdministratorsRoleCeiling TestAdministratorsSelfEdit
TestAdministratorsPasswordAndIdentity TestAdministratorsOutranked
TestAdministratorsOwnPassword
# Plan 03: the last superuser (D-09), lifecycle (D-10), revocation (D-12).
TestAdministratorsLastSuperuser TestAdministratorsLastSuperuserConcurrent
TestAdministratorsLifecycle TestAdministratorsRevocation
# Plan 03: the seam at run time (D-14, D-15), Roles (D-07, D-16, D-17,
# D-21). Plan 04 gap closure: CR-01 code-declared capture and WR-03
# contributed outranking.
TestActionExtensionRun TestActionExtensionOutranked TestRolesLifecycle
TestRolesPermissionCeiling TestRolesCodeDeclaredLocks
TestRolesCodeDeclaredCodeRefused
)
# docker stage: SC-1 regression tests that pass unchanged.
SC1_TESTS=(
TestAdminCreateCommand TestAdminResetPasswordCommand
TestAdminCreateRejectsCrossFieldCollision TestAdminPasswordWithoutFlag
TestAdminAuthLifecycle TestAccountProfileUpdate TestAccountPasswordChange
TestAccountTokens TestPhase10OpenAPIConformance TestMLOpenAPIConformance
)
# admin stage: vitest files that must pass.
VITEST_FILES=(
tests/form/trigger.test.ts
tests/form/PermissionEditorField.test.ts
tests/form/formState.test.ts
tests/form/FormView.test.ts
tests/form/PreviewView.test.ts
)
BROWSER_FILES=(tests/browser/record-actions-wrap.test.ts)
# sc1 stage: the files SC-1 keeps unchanged by this phase, and the phase's
# own files.
SC1_GUARDED=(modules/cabana/commands.go modules/cabana/token_commands.go modules/cabana/account.go)
PHASE_FILES=(
modules/cabana/action_extensions.go modules/cabana/backend_screens.go
modules/cabana/backend_users_admin.go modules/cabana/backend_roles_admin.go
modules/cabana/backend_privileges.go modules/cabana/field_trigger.go
)
# docs stage: the phase's test files, checked for consuming application
# names next to every non-test file under modules/ and docs/ (older test
# files are out of this phase's scope).
PHASE_TEST_FILES=(
modules/cabana/administrators_harness_test.go modules/cabana/administrators_postgres_test.go
modules/cabana/administrators_lifecycle_postgres_test.go modules/cabana/backend_privileges_internal_test.go
modules/cabana/backend_screens_test.go modules/cabana/backend_screens_smoke_test.go
modules/cabana/action_extensions_test.go modules/cabana/action_extensions_postgres_test.go
modules/cabana/roles_postgres_test.go modules/cabana/field_trigger_test.go
modules/cabana/field_trigger_smoke_test.go modules/cabana/record_locks_test.go
modules/cabana/record_locks_smoke_test.go modules/cabana/example_action_extension_test.go
modules/cabana/example_permission_record_locks_test.go
admin/tests/form/trigger.test.ts admin/tests/form/PermissionEditorField.test.ts
admin/tests/form/formState.test.ts admin/tests/form/FormView.test.ts
admin/tests/form/PreviewView.test.ts admin/tests/browser/record-actions-wrap.test.ts
)
# docs stage: names of consuming applications (split so this file does not
# match itself).
APP_NAMES=("p[lł]yta""rium" "fono""teka" "wave""path" "horo""skopia" "bm\\.jakub""zych" "grzyby""funkcjonalne")
usage() {
cat >&2 <<'EOF'
usage:
check-phase19.6.1.sh --self-test
check-phase19.6.1.sh go | docker | admin | docs | sc1
check-phase19.6.1.sh --go | --docker | --admin | --docs | --sc1
check-phase19.6.1.sh --all
EOF
exit 2
}
# detect_go reads go test -json. Exit 1 fail/build, 2 skip, 3 zero/no-tests,
# 4 non-JSON, 5 missing required name.
detect_go() {
python3 - "$1" <<'PY'
import json, os, sys
path = sys.argv[1]
require = [n for n in os.environ.get("REQUIRE_TESTS", "").split() if n]
passed = set()
failed = []
with open(path, encoding="utf-8", errors="replace") as fh:
for raw in fh:
line = raw.strip()
if not line.startswith("{"):
continue
try:
ev = json.loads(line)
except json.JSONDecodeError:
print("refuse: non-json test output", file=sys.stderr)
sys.exit(4)
action = ev.get("Action")
test = ev.get("Test") or ""
pkg = ev.get("Package") or ev.get("ImportPath") or ""
if action == "build-fail" or (action == "fail" and ev.get("FailedBuild")):
print(f"refuse: build failed {pkg}", file=sys.stderr)
sys.exit(1)
text = ev.get("Output") or ""
if action == "output" and "no tests to run" in text:
print(f"refuse: no tests to run in {pkg}", file=sys.stderr)
sys.exit(3)
if action == "skip" and test:
print(f"refuse: skipped {pkg} {test}", file=sys.stderr)
sys.exit(2)
if action == "fail":
failed.append(f"{pkg} {test}".strip())
if action == "pass" and test:
passed.add(test)
if failed:
print("refuse: failed " + ", ".join(failed), file=sys.stderr)
sys.exit(1)
if not passed:
print("refuse: zero tests", file=sys.stderr)
sys.exit(3)
missing = [n for n in require if n not in passed]
if missing:
print("refuse: required tests did not pass: " + ", ".join(missing), file=sys.stderr)
sys.exit(5)
PY
}
# detect_vitest reads a vitest run's output. Exit 1 failed, skipped or todo
# tests, 3 no all-passing summary, 5 a required file did not pass.
# REQUIRE_FILES is a space-separated list. Only the summary and the file
# lines are read: a slow test's title may contain any word.
detect_vitest() {
python3 - "$1" <<'PY'
import os, re, sys
text = open(sys.argv[1], encoding="utf-8", errors="replace").read()
text = re.sub(r"\x1b\[[0-9;]*m", "", text)
need = os.environ.get("REQUIRE_FILES", "").split()
summary = re.findall(r"^\s*(Test Files|Tests)\s+(.*)$", text, re.M)
if any(re.search(r"\b(failed|skipped|todo)\b", rest) for _, rest in summary) or re.search(r"^\s*(FAIL|×)\s", text, re.M):
print("refuse: vitest reports failed, skipped or todo tests", file=sys.stderr)
sys.exit(1)
m = re.search(r"^\s*Tests\s+(\d+) passed \((\d+)\)\s*$", text, re.M)
if not m or m.group(1) != m.group(2) or m.group(1) == "0":
print("refuse: no all-passing vitest summary", file=sys.stderr)
sys.exit(3)
missing = [f for f in need if not re.search(r"^\s*✓ (?:\|\w+\| )?" + re.escape(f) + r" \(\d+ tests?\)", text, re.M)]
if missing:
print("refuse: did not pass: " + ", ".join(missing), file=sys.stderr)
sys.exit(5)
PY
}
# go_json DIR ARGS... runs go test -json ARGS in DIR and checks the stream
# with detect_go.
go_json() {
local dir="$1"
shift
local log err rc=0 dc=0
log="$(mktemp)"
err="$(mktemp)"
(cd "$dir" && go test -json "$@") >"$log" 2>"$err" || rc=$?
detect_go "$log" || dc=$?
if [[ "$rc" -ne 0 || "$dc" -ne 0 ]]; then
cat "$err" >&2 || true
grep -v '^{' "$log" | tail -n 40 >&2 || true
python3 -c 'import json,sys
for raw in open(sys.argv[1], errors="replace"):
try: ev = json.loads(raw)
except ValueError: continue
if ev.get("Action") == "output" and ("FAIL" in ev.get("Output", "") or "SKIP" in ev.get("Output", "") or "_test.go:" in ev.get("Output", "")):
sys.stderr.write(ev["Output"])' "$log" 2>&1 | tail -n 40 >&2 || true
rm -f "$log" "$err"
echo "refuse: go test $* in $dir (test=$rc detect=$dc)" >&2
return 1
fi
echo "go: $(grep -c '"Action":"pass","Package":"[^"]*","Test":"[^"/]*","Elapsed"' "$log") top-level tests passed in $dir ($*)"
rm -f "$log" "$err"
}
# go_named DIR PACKAGES FLAGS NAME... runs exactly the named top-level tests
# and requires every one of them to pass. PACKAGES and FLAGS are word-split.
go_named() {
local dir="$1" pkgs="$2" flags="$3"
shift 3
if [[ "$#" -eq 0 ]]; then
echo "refuse: go_named $pkgs in $dir without required names" >&2
return 1
fi
local names=("$@") regex
regex="^($(IFS='|'; echo "${names[*]}"))\$"
# shellcheck disable=SC2086
REQUIRE_TESTS="${names[*]}" go_json "$dir" $flags -count=1 -run "$regex" $pkgs
}
# go_short DIR PACKAGES runs go test -short PACKAGES in DIR; on failure it
# prints the tail of the output and fails.
go_short() {
local dir="$1" pkgs="${2:-./...}" out
# shellcheck disable=SC2086
if ! out="$(cd "$dir" && go test -count=1 -short $pkgs 2>&1)"; then
printf '%s\n' "$out" | grep -v '^ok ' | tail -n 60 >&2
echo "refuse: go test -short $pkgs in $dir" >&2
return 1
fi
echo "short: $dir ($pkgs)"
}
go_vet() {
local dir="$1"
shift
(cd "$dir" && go vet "$@")
echo "vet: $dir ($*)"
}
expect_detector() {
local detector="$1" name="$2" want="$3" payload="$4" log dc=0
log="$(mktemp)"
printf '%s\n' "$payload" >"$log"
"$detector" "$log" >/dev/null 2>&1 || dc=$?
rm -f "$log"
if [[ "$dc" -ne "$want" ]]; then
echo "refuse: self-test $detector $name: exit $dc, want $want" >&2
return 1
fi
}
# expect_mode NAME WANT_EXIT WANT_TEXT MODE ENV... runs this script in MODE
# with ENV and checks its exit status and that its output carries WANT_TEXT.
expect_mode() {
local name="$1" want="$2" text="$3" mode="$4"
shift 4
local out rc=0
out="$(env "$@" bash "${BASH_SOURCE[0]}" "$mode" 2>&1)" || rc=$?
if [[ "$rc" -ne "$want" ]] || ! grep -qF -- "$text" <<<"$out"; then
printf '%s\n' "$out" | tail -n 10 >&2
echo "refuse: self-test $name: exit $rc (want $want), output must carry \"$text\"" >&2
return 1
fi
}
# sc1_violations reads `git log --name-only --format=@%h` output and prints
# every commit that touches a SC1_GUARDED file and a PHASE_FILES file.
sc1_violations() {
python3 -c '
import sys
guarded = set(sys.argv[1].split())
phase = set(sys.argv[2].split())
commit, files = None, set()
def flush():
if commit and files & guarded and files & phase:
print(commit + " " + " ".join(sorted(files & (guarded | phase))))
for raw in sys.stdin:
line = raw.strip()
if line.startswith("@"):
flush()
commit, files = line[1:], set()
elif line:
files.add(line)
flush()
' "${SC1_GUARDED[*]}" "${PHASE_FILES[*]}"
}
run_self_test() {
bash -n "${BASH_SOURCE[0]}"
expect_detector detect_go pass 0 '{"Action":"pass","Package":"p","Test":"TestRoleCeiling"}'
expect_detector detect_go fail 1 '{"Action":"pass","Package":"p","Test":"TestA"}
{"Action":"fail","Package":"p","Test":"TestAdministratorsSelfEdit"}'
expect_detector detect_go package-fail 1 '{"Action":"pass","Package":"p","Test":"TestA"}
{"Action":"fail","Package":"p"}'
expect_detector detect_go build 1 '{"Action":"build-fail","ImportPath":"p"}'
expect_detector detect_go skip 2 '{"Action":"pass","Package":"p","Test":"TestA"}
{"Action":"skip","Package":"p","Test":"TestAdministratorsLastSuperuser"}'
expect_detector detect_go subtest-skip 2 '{"Action":"skip","Package":"p","Test":"TestAdministratorsLastSuperuser/delete_bulk"}
{"Action":"pass","Package":"p","Test":"TestAdministratorsLastSuperuser"}'
expect_detector detect_go zero 3 '{"Action":"pass","Package":"p"}'
expect_detector detect_go no-tests 3 '{"Action":"output","Package":"p","Output":"testing: warning: no tests to run\n"}
{"Action":"pass","Package":"p"}'
expect_detector detect_go nonjson 4 '{"Action":"pass",'
REQUIRE_TESTS="TestAdministratorsSelfEdit TestAdministratorsLastSuperuserConcurrent" expect_detector detect_go missing-named 5 \
'{"Action":"pass","Package":"p","Test":"TestAdministratorsSelfEdit"}'
REQUIRE_TESTS="TestAdministratorsLastSuperuser" expect_detector detect_go subtest-is-not-parent 5 \
'{"Action":"pass","Package":"p","Test":"TestAdministratorsLastSuperuser/demote_update"}'
local ok=' ✓ tests/form/trigger.test.ts (11 tests) 15ms
✓ |chromium| tests/browser/record-actions-wrap.test.ts (1 test) 147ms
Test Files 2 passed (2)
Tests 12 passed (12)'
REQUIRE_FILES="tests/form/trigger.test.ts tests/browser/record-actions-wrap.test.ts" expect_detector detect_vitest pass 0 "$ok"
REQUIRE_FILES="tests/form/FormView.test.ts" expect_detector detect_vitest missing-file 5 "$ok"
expect_detector detect_vitest failed 1 ' ✓ tests/form/trigger.test.ts (11 tests) 15ms
Test Files 1 failed | 1 passed (2)
Tests 1 failed | 11 passed (12)'
expect_detector detect_vitest skipped 1 ' ✓ tests/form/trigger.test.ts (11 tests | 1 skipped) 15ms
Test Files 1 passed (1)
Tests 10 passed | 1 skipped (11)'
expect_detector detect_vitest no-summary 3 ' ✓ tests/form/trigger.test.ts (11 tests) 15ms'
expect_detector detect_vitest zero 3 ' Tests 0 passed (0)'
# A slow test whose title says "failed" is not a failure.
REQUIRE_FILES="tests/form/trigger.test.ts" expect_detector detect_vitest slow-title 0 ' ✓ tests/form/trigger.test.ts (11 tests) 900ms
✓ announces a failed load 450ms
Test Files 1 passed (1)
Tests 11 passed (11)'
local planted
planted='@aaa1111
modules/cabana/backend_screens.go
modules/cabana/account.go
@bbb2222
modules/cabana/account.go
@ccc3333
modules/cabana/backend_users_admin.go
modules/cabana/crud.go'
if [[ "$(sc1_violations <<<"$planted")" != "aaa1111 modules/cabana/account.go modules/cabana/backend_screens.go" ]]; then
echo "refuse: self-test sc1_violations on planted history" >&2
return 1
fi
# The docker NOT RUN rule: a docker that does not answer.
local fake
fake="$(mktemp -d)"
printf '#!/bin/sh\nexit 1\n' >"$fake/docker"
chmod +x "$fake/docker"
expect_mode not-run 0 "NOT RUN docker" --docker-gate "PATH=$fake:$PATH" PHASE1961_REQUIRE_DOCKER=0
expect_mode require-docker 1 "FAIL docker" --docker-gate "PATH=$fake:$PATH" PHASE1961_REQUIRE_DOCKER=1
rm -rf "$fake"
echo "phase19.6.1 self-test passed"
}
run_go() {
go_vet "$ROOT" ./...
go_short "$ROOT" ./...
(cd "$ROOT" && go -C examples/hello test -count=1 ./...) >/dev/null
echo "hello: examples/hello passed"
go_named "$ROOT" ./modules/cabana "-short" "${SHORT_TESTS[@]}"
echo "phase19.6.1 go passed"
}
docker_up() {
command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1
}
run_docker() {
if ! docker_up; then
echo "refuse: the docker stage needs a reachable Docker daemon (testcontainers); run it on a Docker host" >&2
return 1
fi
go_named "$ROOT" ./modules/cabana "-timeout 20m" "${DOCKER_TESTS[@]}"
go_named "$ROOT" ./modules/cabana "-timeout 20m" "${SC1_TESTS[@]}"
go_json "$ROOT" -count=1 -timeout 20m ./modules/surf
echo "phase19.6.1 docker passed"
}
# docker_gate is the docker step of --all: run the stage when a daemon
# answers, fail with PHASE1961_REQUIRE_DOCKER=1, print NOT RUN otherwise.
docker_gate() {
if docker_up; then
if bash "${BASH_SOURCE[0]}" --docker; then
echo "PASS docker"
return 0
fi
echo "FAIL docker"
return 1
fi
if [[ "${PHASE1961_REQUIRE_DOCKER:-0}" == "1" ]]; then
echo "FAIL docker: no reachable Docker daemon and PHASE1961_REQUIRE_DOCKER=1"
return 1
fi
cat >&2 <<'EOF'
************************************************************************
NOT RUN docker: no reachable Docker daemon on this host.
The Postgres tests of the Administrators and Roles screens did NOT run.
Run on a Docker host (through sudo -u golem when the socket is denied)
before /msd-verify-work:
bash scripts/check-phase19.6.1.sh docker
************************************************************************
EOF
echo "NOT RUN docker (Docker-host only; see the notice above)"
}
# npm_checked NAME DETECT_FILES... runs npm --prefix admin run NAME and
# checks its output with detect_vitest, requiring DETECT_FILES.
npm_checked() {
local script="$1"
shift
local log rc=0 dc=0
log="$(mktemp)"
npm --prefix "$ROOT/admin" run "$script" >"$log" 2>&1 || rc=$?
REQUIRE_FILES="$*" detect_vitest "$log" || dc=$?
if [[ "$rc" -ne 0 || "$dc" -ne 0 ]]; then
grep -E '(FAIL|×|Error|expected)' "$log" | head -n 40 >&2 || true
tail -n 20 "$log" >&2
rm -f "$log"
echo "refuse: npm run $script in admin (test=$rc detect=$dc)" >&2
return 1
fi
echo "vitest ($script): $(grep -E '^\s*Tests\s' "$log" | tr -s ' ')"
rm -f "$log"
}
run_admin() {
local log rc=0
log="$(mktemp)"
npm --prefix "$ROOT/admin" run typecheck >"$log" 2>&1 || rc=$?
if [[ "$rc" -ne 0 ]]; then
tail -n 40 "$log" >&2
rm -f "$log"
echo "refuse: npm run typecheck in admin" >&2
return 1
fi
rm -f "$log"
echo "typecheck: admin"
npm_checked test "${VITEST_FILES[@]}"
npm_checked test:browser "${BROWSER_FILES[@]}"
(cd "$ROOT" && bash scripts/check-admin-openapi.sh --check)
(cd "$ROOT" && bash scripts/check-admin-dist.sh)
echo "phase19.6.1 admin passed"
}
run_docs() {
go_named "$ROOT" ./cmd/summer "" TestDocsTree
(cd "$ROOT" && go run ./cmd/summer docs:build --check)
local pattern hits files=()
pattern="$(IFS='|'; echo "${APP_NAMES[*]}")"
while IFS= read -r -d '' f; do
files+=("$f")
done < <(find "$ROOT/modules" "$ROOT/docs" -type f ! -name '*_test.go' ! -path '*/node_modules/*' -print0)
for f in "${PHASE_TEST_FILES[@]}"; do
if [[ ! -f "$ROOT/$f" ]]; then
echo "refuse: phase test file $f is missing" >&2
return 1
fi
files+=("$ROOT/$f")
done
hits="$(grep -inHIE "$pattern" "${files[@]}" || true)"
if [[ -n "$hits" ]]; then
printf '%s\n' "$hits" | head -n 20 >&2
echo "refuse: a framework source, README, docs page or phase test names a consuming application" >&2
return 1
fi
echo "names: ${#files[@]} files (modules/ and docs/ sources, the phase's tests) checked, no consuming application named"
echo "phase19.6.1 docs passed"
}
run_sc1() {
local base first violations
base="${PHASE1961_BASE:-}"
if [[ -z "$base" ]]; then
first="$(cd "$ROOT" && git log --diff-filter=A --format=%H -- modules/cabana/backend_screens.go | tail -n 1)"
if [[ -z "$first" ]]; then
echo "refuse: no commit adds modules/cabana/backend_screens.go; set PHASE1961_BASE" >&2
return 1
fi
base="$first^"
fi
(cd "$ROOT" && git rev-parse --verify --quiet "$base" >/dev/null) || {
echo "refuse: PHASE1961_BASE $base is not a commit" >&2
return 1
}
violations="$(cd "$ROOT" && git log --name-only --format=@%h "$base..HEAD" | sc1_violations)"
if [[ -n "$violations" ]]; then
printf '%s\n' "$violations" >&2
echo "refuse: SC-1: a phase commit also changes the admin commands or My account" >&2
return 1
fi
echo "sc1: $(cd "$ROOT" && git rev-list --count "$base..HEAD") commits since $(cd "$ROOT" && git rev-parse --short "$base") checked; no phase commit touches ${SC1_GUARDED[*]}"
echo "phase19.6.1 sc1 passed"
}
run_all() {
local stage
for stage in self-test go admin docs sc1; do
if bash "${BASH_SOURCE[0]}" "--$stage"; then
echo "PASS $stage"
else
echo "FAIL $stage"
exit 1
fi
done
docker_gate || exit 1
echo "Phase 19.6.1 gate passed"
}
case "${1:---all}" in
--self-test) run_self_test ;;
--go) run_go ;;
--docker) run_docker ;;
--docker-gate) docker_gate ;;
--admin) run_admin ;;
--docs) run_docs ;;
--sc1) run_sc1 ;;
--all) run_all ;;
self-test) run_self_test ;;
go) run_go ;;
docker) run_docker ;;
admin) run_admin ;;
docs) run_docs ;;
sc1) run_sc1 ;;
all) run_all ;;
*) usage ;;
esac