7. 검증 루프 — '완료'의 정의를 시스템이 잡아 두는 일
운영자가 '됐다'고 느끼는 순간이 가장 위험하다. lint·빌드·테스트·diff 점검까지 자동 발동되는 7단계 검증 루프, 자동 발동의 트리거, 그리고 우회의 흔적.
이 글은 시리즈 Alice 활용법의 7편이다. 6편 멀티에이전트 위임에서 위임의 게이트로 자동 검증이 등장했다. 이 글은 그 검증을 한 단계 끌어올린다 — 직접 한 작업이든 위임한 작업이든, "완료"라는 단어가 입에서 나오는 그 순간 시스템이 강제하는 루프에 대한 기록이다.
0. 검증 루프는 'mind가 완료'라 생각한 순간을 시스템이 다시 확인하게 만드는 일이다
작업이 끝났다고 느끼는 순간이 가장 위험하다. 그 순간 mind는 "이제 됐다"의 안도로 기울어 있고, 누락된 절차를 알아채기 어렵다. lint 안 돌린 채로, 빌드 깨진 채로, 테스트 빠진 채로 "완료"를 적게 된다.
검증 루프는 이 순간을 시스템이 한 번 깨는 장치다. '완료'를 적기 직전에 lint · 빌드 · 테스트 · 보안 · diff 점검을 자동으로 돌리고, 통과해야만 보고가 나갈 수 있게 한다. mind가 완료라고 생각한 것을 시스템이 다시 확인하게 만드는 일. "완료"는 느낌이 아니라 통과된 단계의 수로 결정되어야 하기 때문이다.
이 글은 그 7단계 검증 루프가 어떤 단계로 구성되는지, 자동 발동의 트리거가 무엇인지, 우회는 어떻게 처리되는지에 대한 기록이다.
자동 발동의 메커니즘은 5편 훅과 자동화에서 차용처를 이미 인용한 Anthropic Claude Code의 hooks 시스템이다. 이 글은 그 위에 운영자가 쌓은 검증 단계의 구체에 대한 기록이다.
1. 왜 자동 발동이어야 하는가
검증을 운영자가 의식적으로 호출하면 — 가끔 빼먹는다. 빼먹은 검증이 누적되면 — 어느 순간 prod에서 깨진다. 빼먹는 것이 인지의 한계라면 인지 밖에서 강제해야 한다.
| 검증 방식 | 빼먹을 가능성 | 적합성 |
|---|---|---|
| 운영자가 매번 의식적으로 | 높음 (피곤할 때 / 흐름 탔을 때) | ❌ |
| CI에서만 실행 | 0 — 하지만 commit·push 후라 늦음 | ⚠️ 보조 |
| 보고 직전 훅으로 자동 발동 | 0 — 보고 자체가 차단됨 | ✅ |
세 번째가 답이다. 보고가 나가기 전에 검증을 강제하면 누락이 시스템 레벨에서 막힌다. CI는 보조 안전망이지 1차 방어선이 아니다.
2. 7단계 검증 루프
운영자가 수렴한 단계. 각 단계는 실패 시 즉시 중단하고 명확한 메시지로 보고한다.
2.1 Lint
코드 스타일 · 미사용 변수 · 명백한 오류. 가장 빠르고 가장 자주 잡힌다. 먼저 돌린다 — 실패하면 뒤 단계 시간을 아낀다.
2.2 Type check
TypeScript / mypy / 기타 정적 타입 검사. lint가 못 잡는 더 깊은 오류를 잡는다.
2.3 Build
실제 빌드가 깨지지 않는가. 컴파일러가 통과 못 하는 변경이 머지되지 않게 한다.
2.4 Test
단위 테스트 + 통합 테스트. 통합 테스트는 mocked DB 같은 것이 아니라 실제 자원에 붙여 돌린다. 한 번 mock에 속아 prod 마이그레이션이 깨진 사건이 있었기 때문.
2.5 보안 점검
시크릿 하드코딩 · SQL injection 패턴 · 운영자 실명 등 leak 위험 grep. 8개 체크리스트가 매 변경마다 자동으로 돈다. 한 가지라도 잡히면 즉시 중단.
2.6 Diff 검토
변경된 라인이 운영자의 의도와 일치하는가. 의도 외 변경이 끼어들지 않았는가. 사람이 마지막으로 보는 게이트.
2.7 보고 출력 점검
보고문 자체에 leak 위험(시크릿 · 내부 식별자 · 실명)이 없는가. 보고가 발신되기 직전 한 번 더 grep.
이 7단계를 통과해야 "완료" 보고가 나갈 수 있다. 어느 한 단계에서 실패하면 — 보고는 막히고, 운영자에게 "X 단계 실패" 한 줄이 전달된다.
3. 자동 발동 트리거 — 언제 발동되는가
검증 루프가 자동으로 발동되는 조건.
3.1 명시적 트리거 — "완료" 표현 직전
운영자가 "끝났습니다" / "완료" / "done" / "ready" 같은 표현을 출력하려는 그 시점에 훅이 끼어든다. 출력 직전이지 출력 후가 아니다 — 출력 후라면 늦다.
3.2 명시적 트리거 — PR 준비 / 머지 가능 보고
"PR 준비됐습니다" 같은 메시지도 같은 훅이 발동시킨다. PR이 나간 뒤 검증이 도는 게 아니라, 나가기 전에.
3.3 명시적 트리거 — 커밋 가능 보고
"커밋 가능 상태입니다" 같은 표현도. 커밋 자체를 막는 건 아니지만, 그 보고가 운영자에게 가기 전에 검증을 한 번 더 강제.
3.4 명시적 호출 — /verify
운영자가 의식적으로 검증하고 싶을 때는 슬래시 커맨드로 같은 루프를 호출할 수 있다. 훅과 같은 절차를 가리키는 두 입구.
3.5 두 입구는 서로 대체되지 않는다
훅은 보고 직전에 발동되고, 슬래시 커맨드는 운영자가 의식적으로 끌어 쓸 때 발동된다. 같은 절차, 반대 방향 — push vs pull. 둘이 잡는 순간이 다르다는 걸 운영하면서 알게 됐다. 훅은 안도의 순간을 잡는다. mind가 이미 "이제 됐다"로 기울어 있는 시점. 슬래시 커맨드는 의심의 순간을 잡는다. mind가 "내가 실제로 검증했나" 다시 묻는 시점. 두 순간은 워크플로의 서로 다른 곳에 있고, 한 쪽만 잡는 시스템은 실패의 절반을 놓친다.
실제 운영에서는 훅이 슬래시 커맨드보다 훨씬 자주 발동된다 — 안도의 순간은 모든 작업의 끝마다 등장하지만, 의심의 순간은 더 드물다. 그러나 슬래시 커맨드는 운영자가 검증을 통제하는 입구다. 자동 발동 조건 밖에서도 의식적으로 끌어 쓸 수 있는 자리. 둘이 합쳐서야 표면이 다 덮인다 — 시스템이 강제하는 쪽과 의지로 호출하는 쪽.
4. 자동 발동을 SKIP하는 경우
모든 작업에 검증이 필요한 것은 아니다. 다음 경우에는 자동 발동을 SKIP하도록 명시한다.
- 읽기 전용 작업 — 스캔 · 검토 · 조사. 코드 변경이 없으니 검증할 게 없다.
- 문서만 수정한 변경 —
.md파일만 만진 경우. lint·빌드 의미 없음. - 의도적 임시 스크래치 — 실험 코드, prototype. 운영자가 명시.
- 운영자가 "검증 생략" 지시한 경우 — 명시적 의지로 우회.
SKIP 조건은 페르소나에 명시되어 있어 훅이 알아서 판단한다. 운영자가 매번 "이건 SKIP" 말할 필요는 없다.
5. 우회의 흔적
가끔 운영자가 의도적으로 검증을 우회해야 할 때가 있다 — 디버깅 중이거나, 임시 작업이거나, hot fix 중이거나.
우회는 가능하다. 그러나 우회한 사실은 항상 로그에 남는다.
[verify] BYPASSED at 2026-05-17 14:23 — reason: emergency hotfix
[verify] Skipped: lint / build / test / security / diff / report
[verify] Note: re-run /verify manually after hotfix lands이렇게 남기면 — 우회가 무엇이었는지 추적 가능하고, "왜 검증 안 됐지" 같은 사후 조사가 쉽다. 우회를 막지는 않되, 흔적은 남긴다.
우회가 가능하지 않으면 운영자는 검증 자체를 꺼 버린다. 일단 꺼지면 영영 안 켜진다.
흔적에는 우회의 순간 너머의 두 번째 쓰임이 있다. hotfix가 끝나고 한 주가 지나면 — 어느 모서리가 잘려나갔는지 mind에서 사라진 시점에 — 우회 로그가 유일하게 남아 있는 증거가 된다. 3주 전 우회 항목으로 되돌아가서 무엇이 빠졌고, 왜 빠졌고, 사후 검증이 그것을 따라잡았는지를 다시 따라가 본 적이 있다. 흔적이 없으면 이 되짚기는 불가능하다 — 운영자는 그저 "아무것도 놓치지 않았다"고 믿어야 하고, 그건 검증하지 않은 것과 같다.
6. 실패 후 처리
검증이 실패했을 때 운영자가 받는 것.
[verify] FAILED at step: Test (3/7)
[verify] 2 tests failed:
- integration/auth.test.ts > "github oauth callback redirects to next"
- integration/db.test.ts > "rls policy blocks other user's row"
[verify] Full log: /tmp/verify-2026-05-17-1423.log
[verify] Report blocked.핵심은 — 실패 정보가 한 화면에 압축된다는 점이다. 어디서 실패했는지, 무엇이 문제인지, 자세한 로그는 어디에 있는지. 운영자는 그 한 화면만 보고 다음 결정 가능 (수정 / 우회 / 임시 SKIP).
긴 로그를 콘솔에 다 뱉으면 운영자가 다시 그것을 요약하느라 시간을 쓴다. 그 자체가 새 부담.
7. 함정 — 검증 루프가 잘못되는 패턴
7.1 너무 느린 검증
검증이 30초 이상 걸리면 — 운영자가 "이거 너무 느리네" 생각하면서 우회를 자주 쓰게 된다. 자주 쓰면 우회가 기본이 되고, 검증의 의미가 사라진다. → 가장 자주 잡히는 단계를 먼저, 비싼 단계는 뒤로. 7단계 중 실패는 보통 lint·type에서 잡힌다.
7.2 false positive 많음
검증이 자주 틀린 알람을 내면 — 운영자는 검증을 의심하기 시작한다. 의심하면 우회. → 검증 자체의 신뢰도가 높아야 한다. 한 번 false alarm이 잡히면 즉시 검증 로직 자체를 수정.
7.3 검증 결과가 단순 통과/실패만
"실패"만 알려 주고 어디서 왜 실패했는지 모호하면 — 운영자가 직접 로그를 뒤져야 한다. 부담이 다시 운영자에게. → 출력은 짧되 구체적이어야 한다 (어느 단계, 어느 테스트, 어느 라인).
7.4 SKIP 조건이 너무 좁음
검증이 거의 매번 발동되면 운영자가 피곤해진다. 읽기 전용 작업까지 검증하면 — 검증 자체가 잡음. → SKIP 조건을 페르소나에 충분히 명시.
7.5 검증이 검증 자체를 검증하기
검증 하네스 자체도 코드이고, 그 코드도 회귀할 수 있다. lint 룰이 발동은 하지만 안에서 silently 깨져 있거나, 테스트 러너가 업그레이드 중이라 절반의 스위트를 skip하고 있으면 — 루프는 "passed"를 돌려주지만 실제 점검은 돌지 않은 상태가 된다. 운영자는 초록불을 보고 그대로 ship한다.
이 자리에는 깔끔한 답이 없다 — 검증자를 검증한다는 시도는 무한 후퇴다. 도움이 되는 건 검증 스크립트 자체에 대한 어떤 수정이라도 최고 위험 변경으로 다루는 것이다. 8개 보안 체크리스트가 그대로 적용되고, 하네스 자체에 대한 통합 smoke test가 돌고, "verify 리팩토링했는데 잘 동작하네요" 같은 보고가 나오는 그 자리에서 속도를 늦춰야 한다. 함정은 검증 도구의 사용성이 운영자를 "검증자를 조용히 고치고 넘어가자" 쪽으로 미는 것이다. 시스템은 반대 방향으로 밀어야 한다 — 검증자 변경은 더 적은 점검이 아니라 더 많은 점검을 받는다.
8. 한 원칙으로 압축
검증 루프 설계의 핵심은 한 문장으로 줄어든다.
"운영자가 '완료'라고 말하려는 순간 시스템이 한 번 끼어든다. 통과해야만 그 말이 나갈 수 있다. 우회는 가능하지만 흔적이 남는다."
이 원칙이 지켜지면 검증 루프는 운영자의 안도를 시스템이 한 박자 멈추게 하는 안전망이 된다. 지켜지지 않으면 — 검증을 꺼 두거나, false alarm으로 의심받거나, 우회가 기본이 된다. 완료는 선언이 아니라 시스템이 강제한 사실이어야 한다고 생각한다.
이 루프가 오래 살수록 그것이 없는 환경을 상상하기 어려워진다. 자동 발동을 6개월 살린 후에는 안도의 순간이 행동의 신호로 더 이상 느껴지지 않게 된다 — mind가 "완료"는 내 단어가 아니라 시스템의 단어라는 패턴을 내재화한다. 어쩌면 이 내재화 자체가 루프의 진짜 산물이다. 잡힌 개별 버그를 넘어서. 절차의 규율이 운영자가 스스로에게 강제하는 것에서 환경이 조용히 보장하는 것으로 옮겨간다.
다음 글에서는 검증·메모리·스킬·훅 모두가 의존하는 가장 근본적인 자원 — 토큰 경제, 즉 컨텍스트 윈도우에 무엇을 들이고 무엇은 막을지에 대해 다룬다.