[AI 개발 거버넌스 (6)] 기계가 규칙을 지킨다 — 데드 버튼 탐지와 3-Tier CI

2026/06/12 AI Development Governance 3886자 · 약 12분

Hook

“데드 버튼 만들지 마세요.” 이렇게 당부하면 다음 PR에도 데드 버튼이 나옵니다. “목 데이터 지워주세요.” 그래도 mock 변수가 프로덕션에 들어갑니다. 사람에게 규칙을 맡기면 실패합니다.

세 프로젝트에서 얻은 가장 중요한 교훈: 규칙은 기계가 지키게 하고, 사람은 판단에만 쓴다. 이 글에서는 pre-commit부터 배포까지 3-Tier 게이트로 규칙을 기계적으로 강제하는 구조를 공유합니다.

TL;DR

  • 사람은 판단에만 쓰고, 규칙 준수는 기계에 위임 — 3-Tier 게이트: 자동화(pre-commit+CI) → 인간 리뷰 → 보안/법률
  • 데드 버튼 정적 탐지: 빈 onclick, href="#", TODO 인터랙티브 요소를 CI가 자동 차단
  • 목 데이터 정적 탐지: mock/dummy/fake/sample 변수명 패턴으로 프로덕션 잔류 차단
  • 의존성 없는 검증 스크립트(tools/check.js)가 CI/로컬/hook 3곳에서 동일 실행

Background: 사람이 규칙을 지키지 못하는 이유

반복되는 실패 패턴

개발자/AI의 심리:
  "이 버튼은 임시로 비워둘게요"     → 까먹음 → 데드 버튼 배포
  "이건 나중에 진짜 API로 바꿀게요" → 까먹음 → mock 변수 잔류
  "이 시크릿은 잠깐만 하드코딩"     → 까먹음 → API 키 노출
규칙사람 의존 시 실패율기계 강제 시 실패율
데드 버튼 없음~40% (PR당)0% (CI 차단)
목 데이터 없음~20%0% (정적 탐지)
시크릿 하드코딩 없음~10%0% (스캔)
커밋 메시지 규칙~30%0% (hook 강제)

Solution: 3-Tier CI 게이트

전체 구조

Gate 1: 자동화 (기계)
┌─────────────────────────────────────────────────────┐
│ pre-commit hook          →   CI Pipeline             │
│ ├── lint + format fix         ├── lint (엄격)        │
│ ├── typecheck                 ├── typecheck          │
│ ├── 시크릿 스캔               ├── test:unit           │
│ ├── 커밋 메시지 검증          ├── test:components     │
│ └── .env 커밋 차단            ├── test:contracts     │
│                               ├── audit              │
│                               └── build dry-run      │
└─────────────────────────────────────────────────────┘
         ↓ 통과 시
Gate 2: 인간 리뷰
┌─────────────────────────────────────────────────────┐
│ ├── CODEOWNERS 승인 (1~2명)                         │
│ ├── 디자인 체크리스트                               │
│ ├── 수동 브라우저 테스트                             │
│ └── PR 500라인 이하 권장                             │
└─────────────────────────────────────────────────────┘
         ↓ 통과 시
Gate 2.5: 보안/법률
┌─────────────────────────────────────────────────────┐
│ ├── OWASP Top 10 통과                               │
│ ├── 하드코딩 시크릿 없음                             │
│ ├── 파라미터화된 SQL만                               │
│ ├── 법적 제약 준수                                   │
│ └── AI 생성 명시                                    │
└─────────────────────────────────────────────────────┘
         ↓ 통과 시
    배포 (dev → staging → prod)

Gate 1: pre-commit 훅 구성

카테고리검사 내용
일반 파일대용량 파일 거부 (1MB→R2), 병합 충돌, JSON/YAML/TOML 검증, EOF/후행공백
시크릿 스캔detect-secrets (16개 감지 플러그인: AWS/GitHub/JWT/Stripe 등)
커밋 메시지Conventional Commits type 화이트리스트 강제
코드 품질ESLint/Biome --fix, Prettier, tsc --noEmit
의존성 감사pnpm audit --audit-level=high (push 단계만)
환경 변수.env/.dev.vars 커밋 차단

핵심 기능 1: 데드 버튼 정적 탐지

AI가 만드는 가장 흔한 안티패턴 — 반응 없는 버튼을 CI에서 정적 탐지합니다:

// tools/check.js — 데드 버튼 탐지 로직 (개념)

const DEAD_BUTTON_PATTERNS = [
  /onclick=["']\{\}["']/,           // onclick="{}"
  /onclick=["']\s*["']/,             // onclick=""
  /href=["']\#["']/,                 // href="#"
  /href=["']javascript:void\(0\)/,  // href="javascript:void(0)"
  /<button>[^<]*<\/button>\s*<!--\s*TODO/, // TODO 버튼
];

// HTML 파일 스캔 → 패턴 매칭 → 실패

탐지 대상:

패턴설명조치
onclick=""빈 이벤트 핸들러❌ 차단
href="#"의미 없는 링크❌ 차단
href="javascript:void(0)"안티패턴❌ 차단
TODO가 있는 <button>미구현 버튼❌ 차단
disabled + title 명시의도적 비활성✅ 통과

핵심 기능 2: 목 데이터 정적 탐지

프로덕션 코드에 남는 mock/dummy/fake/sample 변수를 탐지합니다:

// 탐지 대상 변수명 패턴
const MOCK_PATTERNS = [
  /\bmock[A-Z]/,      // mockData, mockUser
  /\bdummy[A-Z]/,     // dummyData
  /\bfake[A-Z]/,      // fakeResponse
  /\bsample[A-Z]/,    // sampleUser
  /\btest[A-Z]Data/,  // testData
];

테스트 파일(*.test.js, *.spec.js)은 예외 대상에서 제외합니다.

핵심 기능 3: 의존성 없는 검증 스크립트

피트니스 PWA에서 사용하는 tools/check.js의 핵심 설계:

왜 의존성이 없어야 하는가?

  • npm install 없이 실행 가능 → 로컬, CI, hook 어디서든 동일하게 동작
  • Node 표준 모듈(fs, path)만 사용 → 유지보수 부담 최소
  • CI와 로컬에서 다른 결과가 나오는 “환경 문제” 원천 차단

9개 검증 카테고리

tools/check.js 검증 항목 전체
#카테고리검증 내용실패 시
1파일 크기개별 파일 400KB 초과하드 실패
2manifest 스키마필수 필드/타입하드 실패
3HTML 구조필수 요소 존재하드 실패
4데드 버튼빈 onclick/href=”#”하드 실패
5목 데이터mock/dummy/fake/sample 변수하드 실패
6SW precache모든 자산 캐시 포함하드 실패
7기능 회귀핵심 데이터 구조 보존하드 실패
8i18n 구조번역 파일 정합성하드 실패
9사용자 데이터LocalStorage 키 보존하드 실패

CI/CD 파이프라인 흐름

PR 생성
  └── lint · typecheck · test · audit · build dry-run
       └── 통과 → 인간 리뷰
            └── 승인 → main 머지
                 └── deploy:dev 자동 + migrate:dev
                      └── 태그 v*.*.*
                           └── deploy:staging (수동 승인)
                                └── smoke test (핵심 여정)
                                     └── deploy:prod (테크 리드 수동 승인)

즉시 롤백 기준

조건조치
에러율 > 10%즉시 롤백
결제 기능 장애즉시 롤백
핵심 API 5xx > 5%즉시 롤백
로그인(OAuth) 실패즉시 롤백
데이터 손실즉시 롤백

롤백 우선 원칙: 근본 원인 파악 전에 먼저 복구. 포스트모템은 비난 없음(blameless).

게이트 우회 (핫픽스만)

# 긴급 핫픽스 시에만 허용
SKIP_PREFLIGHT=1 git push
# 또는
git push --no-verify

남용 금지, 사유 명시 의무. 정기 배포: 화/목 10:00-14:00 KST (저트래픽).

Result: 자동화 도입 전후

지표수동 검증 (Before)자동화 (After)
데드 버튼 배포PR당 5~8개0개
목 데이터 잔류월 1~2건0건
시크릿 노출 사고분기별 1~2건0건
PR 리뷰 시간 (규칙 검증)리뷰어가 수동 체크자동 (0초)
커밋 메시지 규칙 위반~30%0%

Takeaway

  1. “데드 버튼 만들지 마세요”보다 CI가 차단하는 것이 확실합니다 — 사람에게 규칙을 맡기면 “이번만 예외”가 반복되고, 결국 규칙이 무너집니다. 반면 CI가 onclick="" 패턴을 감지해서 PR을 자동 차단하면, 예외가 불가능합니다. 규칙의 90%는 자동화할 수 있고, 자동화된 규칙은 100% 지켜집니다. 사람의 에너지는 자동화할 수 없는 판단(아키텍처, 도메인 설계, 코드 리뷰)에 집중해야 합니다

  2. 검증 스크립트는 의존성이 없어야 합니다npm install이 필요한 검증은 CI와 로컬에서 다른 결과를 낳을 수 있습니다. Node 표준 모듈만 사용하는 검증 스크립트는 어디서든 동일하게 동작하며, 설치 실패나 버전 충돌로 인한 “환경 문제”를 원천 차단합니다. 이 설계 원칙 하나로 “로컬에서는 통과하는데 CI에서는 실패함”이라는 가장 짜증나는 상황을 없앨 수 있습니다

  3. 3-Tier 게이트는 속도 저하가 아니라 품질 향상입니다 — “게이트가 너무 많으면 개발이 느려진다”는 우려가 있지만, 실제로는 역입니다. 자동화된 게이트가 규칙 검증을 0초로 만들기 때문에, 리뷰어는 규칙 준수가 아닌 코드 품질에만 집중할 수 있습니다. PR당 리뷰 시간이 오히려 단축됩니다. 게이트가 없으면 리뷰어가 “이 버튼 작동하나요?”를 확인해야 하고, 게이트가 있으면 “이 접근이 최선인가?”에만 집중합니다


← 이전시리즈: AI 주도 개발 거버넌스다음 →
(5) 레이어드 테스팅(6) 3-Tier CI 게이트(7) ADR 14선
HeonJe Lee | 선임연구원
게이트웨이 On-promise 제품 팀에서 시스템 모니터링 및 관리를 쉽게 다가갈 수 있도록 하기 위한 업무를 하고 있습니다.

Contact: lhjnano@gmail.com

Search

    Table of Contents