set -euo pipefail
cd "$(git rev-parse --show-toplevel)"
S=insuniverse-server
A=insuniverse-admin
G=.claude/plans/samsung-account-intake-hardening-m4.gates.mjs
B=.claude/plans/samsung-account-intake-hardening-m4.baseline.json
DIRS=(apps/api/src/modules/auth apps/api/src/modules/admin apps/api/src/modules/menu apps/api/src/modules/fc/agency apps/api/src/modules/alert apps/api/src/seeds)
MY="sa-m4-gate-$$"
MYP=24307
T=$(mktemp -d)
trap 'rm -rf "$T"; find "$S/apps/api/src" -name "*.__cf_h4__.ts" -delete; docker rm -f "$MY" >/dev/null 2>&1 || true' EXIT
#* 프런트 실검사 설정 — admin tsconfig.json 은 files:[] 라 아무것도 안 본다(M3 관측).
printf '{"extends":"%s/%s/tsconfig.node.json","compilerOptions":{"composite":false,"noEmit":true}}\n' "$PWD" "$A" >"$T/adm.json"
#* rc 는 함수 안 `set +e … set -e` 로만 잡는다 — 도구 안 인라인 서브셸은 set -e 가 무시된다(실측).
srv_jest() { set +e; ( cd "$S" && npx jest --silent --json --outputFile="$1" "${@:2}" ) >/dev/null 2>&1; set -e; }
srv_tsc() { set +e; ( cd "$S" && npx tsc --noEmit -p tsconfig.json ) >"$1" 2>&1; echo $? >"$1.rc"; set -e; }
adm_tsc() { set +e; ( cd "$A" && npx tsc -p "$T/adm.json" --listFiles ) >"$1" 2>&1; echo $? >"$1.rc"; set -e; }

# 검사 #1: 착수 기준선 — 추적 파일 clean 에서만 동결 · 기준 커밋 3벌 · 붉음 집합 · 미추적 기록
# 기대: `#1 ok  base=<8> srv=<8> adm=<8> · jest 실패 <n> · tsc{srv:<k>,adm:<m>}서명 · 미추적 기록 <u>`. 기준선이 있으면 다시 뜨지 않고 보고만 한다.
# 음성: 계획 창 실측(2026-09-22) — 기준선 없이 `baseline-report` 는 `#1 FAIL: 기준선 … 를 읽지 못했다` rc=1,
#       PRD 표를 고친 뒤 커밋 전에 `clean` 은 `#1 FAIL: 부모에 미커밋 추적 변경이 있다 — .claude/prds/samsung-account-intake-hardening.prd.md` rc=1.
if [ ! -f "$B" ]; then
  node "$G" clean
  srv_jest "$T/j0.json" "${DIRS[@]}"
  srv_tsc "$T/t0"
  adm_tsc "$T/a0"
  node "$G" baseline "$B" "$T/j0.json" "$T/t0" "$(cat "$T/t0.rc")" "$T/a0" "$(cat "$T/a0.rc")"
fi
node "$G" baseline-report "$B"

# 검사 #2: 서버 jest 회귀 — auth · admin · menu · fc/agency · alert · seeds 스위트의 실패 식별자가 기준선 밖으로 늘지 않았다 · 기계 고장은 초록이 아니다
# 기대: `#2 ok  실패 테스트 0건 전건 기준선 안(식별자 대조) · 신규 실패 0 · 실행 <t>건` (관측 14 — 기준 붉음 0). 실 MySQL spec 은 여기서 skip 이다.
# 음성: 계획 창 실측 — 실패 0 인 합성 기준선과 access it 하나를 failed 로 바꾼 실제 보고서로
#       `#2 FAIL: 신규 실패 테스트 1 — …samsung-intake-access.spec.ts::…allowlist 가드가 둘 다 걸려 있다` rc=1, 빈 보고서 `{}` 는
#       `#2 FAIL: jest --json 보고서가 없거나 테스트 0건 — …` rc=1. 실제 보고서 그대로는 `#2 ok … 실행 550건`.
srv_jest "$T/j.json" "${DIRS[@]}"
node "$G" jest-regress "$B" "$T/j.json"

# 검사 #3: 타입 축 — 서버 범위(admin · menu · auth/admin · fc/agency · alert · seeds · main · app.module · 에러 상수) 에러 0 · 서명 계수 기준선 이하 · 프런트 4파일이 tsc 프로그램에 실렸고 에러 0
# 기대: `#3 ok  서버 서명 <k>건·범위 0 · 프런트 파일 4종 실림·에러 0·서명 <m>건 기준선 이하`.
# 음성: 계획 창 실측 — 빈 기준선에 범위 안 서버 에러 한 행(`apps/api/src/modules/admin/x.ts(1,1): error TS2554: …`)을 주면
#       `#3 FAIL: 서버 범위 안 tsc 에러 — apps/api/src/modules/admin/x.ts` rc=1, 빈 기준선에 실제 서버 로그를 주면
#       `#3 FAIL: 서버 tsc 서명이 기준선보다 늘었다 8 — …terms.repository.spec.ts|TS2322|…` rc=1.
srv_tsc "$T/t"
adm_tsc "$T/a"
node "$G" tsc-regress "$B" "$T/t" "$(cat "$T/t.rc")" "$T/a" "$(cat "$T/a.rc")"

# 검사 #4: 판정 · 가드 · 계정 보호 · 표시 · DTO · 메뉴 · seed 단위 · 알림 규칙 · 인테이크 접근 — 필수 it 35건이 이름·상태·단정 계수로 통과
# 기대: `#4 ok  필수 it 35건 통과 · 전건 단정 1건 이상`. 같은 실행의 다른 it 실패도 붉다.
# 음성: 계획 창 실측 — 현재 스위트 보고서로 `#4 FAIL: 필수 it 누락 34 — judge: admin 역할이고 …` rc=1(isPublic it 하나만 이미 있다).
srv_jest "$T/s.json" "${DIRS[@]}"
node "$G" spec-names '#4' "$T/s.json"

# 검사 #5: 실 MySQL — 격리 컨테이너(8.0.42)에서 DDL 멱등 · 동시 기동 · 유일 · 값 제약 · 재생성 계정 · 관리자 생성 · 잠금 상한 · 판정 · 로그인 조회 9건이 skip 아닌 통과
# 기대: `#5 ok  필수 it 9건 통과 · 전건 단정 1건 이상`. 포트가 쥐여 있거나 도커가 없으면 `docker run` 에서 멈춘다(초록 아님).
# 음성: 계획 창 실측 — spec 이 없어 `#5 FAIL: 필수 it 누락 9 — mysql: 두 번 실행해도 …` rc=1. env 없이 돈 선례 spec(loss-analysis mysql-cases, 11건 pending)의
#       제목만 이 9건으로 바꾼 보고서는 `#5 FAIL: mysql: 두 번 실행해도 …=pending / …` rc=1 — skip 은 초록이 아니다.
docker run -d --rm --name "$MY" -e MYSQL_ALLOW_EMPTY_PASSWORD=1 -e MYSQL_DATABASE=super_admin_seed -p "127.0.0.1:${MYP}:3306" mysql:8.0.42 --log-bin-trust-function-creators=1 >/dev/null
READY=0
for i in $(seq 1 120); do
  if docker exec "$MY" mysql --protocol=TCP -h127.0.0.1 -uroot super_admin_seed -e 'SELECT 1' >/dev/null 2>&1; then READY=1; break; fi
  sleep 1
done
[ "$READY" = 1 ] || { echo "#5 FAIL: 격리 MySQL 이 ${i}초 안에 TCP 로 서지 않았다"; false; }
export SUPER_ADMIN_SEED_MYSQL_PORT="$MYP"
srv_jest "$T/m.json" apps/api/src/seeds/super-admin.seed.mysql.spec.ts
unset SUPER_ADMIN_SEED_MYSQL_PORT
node "$G" spec-names '#5' "$T/m.json"

# 검사 #6: 실기동 — 빌드 산출물을 격리 DB 에 세 번 띄운다. A 불일치 멈춤·API 응답 · B 지정·인테이크·전체 메뉴·계정 보호·목록 표시 · C 이미 지정·칸 한 벌
# 기대: `#6 ok  기동 A 불일치 멈춤·API 응답·인테이크 403·매핑 메뉴 2 · 기동 B 지정 1행·… · 기동 C 이미 지정·칸/인덱스/제약 1/1/1`.
# 음성: 계획 창 실측 — 구현 전 산출물로 `#6 FAIL: 기동 A 로그에 [seed:super-admin] 결과 줄이 30초 안에 없다 — seed 가 API 기동 경로에서 돌지 않았다` rc=1(37초).
set +e; ( cd "$S" && npx nest build ) >"$T/build.log" 2>&1; BRC=$?; set -e
[ "$BRC" = 0 ] || { echo "#6 FAIL: nest build rc=$BRC — $(tail -3 "$T/build.log")"; false; }
node "$G" boot "$MY" "$MYP" "$T"

# 검사 #7: 범위 · 원본류 첨부 — 기준 커밋 대비 서브모듈 변경이 §Files to Change 안 · xlsx/tsv/csv/txt/png/sql 은 표와 무관하게 막는다
# 기대: `#7 ok  서브모듈 변경 <c>경로 전건 표 안(표 34행) · 요청 원본류 첨부 0 · 부모 변경 <p>경로`. `#8` 이 사본을 쓰기 **전에** 돈다.
# 음성: 계획 창 실측 — 기준선 없이 `#7 FAIL: 기준선 … 를 읽지 못했다` rc=1, 미추적을 기록하고 서버 기준을 HEAD~3(M1 전)로 둔 합성 기준선은
#       `#7 FAIL: 표 밖 서브모듈 변경 8 — …agency-fc.repository.ts …samsung-intake-parse.spec.ts …samsung-intake.dto.ts` rc=1.
node "$G" scope "$B"

# 검사 #8: 반사실 — 판정 · 계정 보호 · seed 로그인 대조 한 문장씩을 뒤집은 사본 3벌에서 판별 it 4건만 붉고 무관 it 6건은 초록
# 기대: `#8 ok  판정·보호·대조 한 문장을 끈 사본 3벌에서 붉음 4건(판별) · 초록 6건(…)`. 앵커가 정확히 1회가 아니면 사본 생성에서 FAIL.
# 음성: 계획 창 실측 — 구현 전이라 `#8 FAIL: apps/api/src/modules/admin/super-admin.service.ts 가 없다 — 반사실 사본을 만들 수 없다` rc=1(사본 없음).
node "$G" write-cf "$T/jest.cf.js"
set +e; ( cd "$S" && npx jest --silent --config "$T/jest.cf.js" --json --outputFile="$T/cf.json" apps/api/src/modules/admin/super-admin.service.spec.ts apps/api/src/modules/admin/admin-super.spec.ts apps/api/src/seeds/super-admin.seed.spec.ts ) >/dev/null 2>&1; set -e
node "$G" counterfactual "$T/cf.json"
find "$S/apps/api/src" -name '*.__cf_h4__.ts' -delete

# 검사 #9: 서버 정적 계약 — 인테이크 env·가드 흔적 0 · 컨트롤러 가드 · seed 는 main.ts listen 뒤 한 곳·수명주기 훅 0 · seed 본문은 실행기만 부름 · tn_admin 쓰기는 관리자 리포지터리·seed 뿐이고 수정·삭제 호출은 AdminService 뿐 · 칸이 스키마·엔티티·DTO 밖 · raw SQL 두 파일 · 새 파일 env 읽기 0 · 전역 모듈·규칙 등록 · E0827 CRLF
# 기대: `#9 ok  인테이크 env·가드 흔적 0·… · E0827 CRLF`.
# 음성: 계획 창 실측 — `#9 FAIL: 인테이크 env 가드 흔적 5 — …samsung-intake-admin.guard.spec.ts …samsung-intake-admin.guard.ts …samsung-intake-access.spec.ts …samsung-intake-admin.controller.ts …samsung-intake.service.ts` rc=1.
#       범위 팔만 앞에 뗀 사본은 지금 코드에서 초록이고, `d.update(tnAdmin)` 한 줄 합성 파일에 `tn_admin 쓰기가 관리자 리포지터리·seed 밖에 있다`,
#       `adminRepository.delete(` 한 줄에 `관리자 수정·삭제 쓰기를 보호 한 자리(AdminService) 밖에서 부른다` rc=1.
node "$G" server-contract

# 검사 #10: 프런트 계약 — 응답 타입 admIsSuper · 생성·수정 입력 부재 · 목록 구분 열(두 낱말) · 상세 읽기 전용 칸·최고관리자 계정에서만 보호 안내 · 수정 요청 무변경 · 인테이크 주석 · 단축키 안내 0 · 저장소 0
# 기대: `#10 ok  응답 타입 admIsSuper · … · 저장소·HTML 주입 0`.
# 음성: 계획 창 실측 — `#10 FAIL: adminApi.ts GetAdminReturn 의 admin 에 admIsSuper: boolean 이 없다` rc=1.
node "$G" front-contract
