바이브 코딩의 속도감은 실제다. LLM 챗에 요구사항을 던지면 React 컴포넌트가 나오고, Tailwind 클래스가 붙고, 얼핏 완성처럼 보이는 화면이 완성된다. 문제는 그 다음이다. 그 코드가 키보드로 탐색 가능한지, aria 속성이 제대로 연결되어 있는지, 확장 요청이 들어왔을 때 일관성을 유지할 수 있는지—속도가 올라간 순간부터 이 질문들이 더 빠른 속도로 쌓인다.
dev.to에 올라온 실험 하나가 이 문제를 구체적인 수치로 보여준다. Toolcrib v0.5.0과 무통제 AI 생성 코드를 Puppeteer로 브라우저 환경에서 비교한 결과, 원시 HTML 엘리먼트 수는 91.2% 줄었고, 손으로 작성한 스타일 속성은 100% 제거됐다. 156개 패치가 모두 클린하게 적용됐다. 이 숫자만 보면 단순한 리팩토링 성과처럼 읽힌다. 하지만 실험의 핵심은 다른 데 있다.
확장 테스트가 결정적이었다. 삭제 확인 다이얼로그 하나를 추가해달라고 AI에게 요청했을 때, 무통제 생성 코드는 새로운 원시 엘리먼트 11개와 className 11개를 쏟아내면서 이전에 수정했던 접근성 버그를 그대로 반복했다. 반면 컴포넌트 기반 접근에서는 <AlertDialog> 프리미티브 하나로 매핑되어 새로운 스타일 속성이 0개였다. 이 차이는 품질의 문제가 아니다. 구조의 문제다. 검증된 바닥이 없으면 AI는 매 요청마다 같은 실수를 새로 만들어낸다.
접근성 버그를 사후에 고치는 비용도 실측됐다. 포커스 트래핑과 키보드 이벤트를 직접 관리하는 커스텀 인프라 코드 86줄이 필요했다. 그리고 그 해결책은 여전히 fragile하다. Select 트리거의 명시적 ID, aria-invalid와 aria-describedby 연결, 정렬 가능한 테이블 헤더의 tabIndex={0}과 aria-sort 상태—이것들은 AI가 처음부터 신경 써야 할 영역이 아니라, 컴포넌트 라이브러리가 이미 해결해뒀어야 할 영역이다. 바이브 코딩의 속도를 지속 가능하게 만들려면, 그 아래에 미리 검증된 컴포넌트 플로어가 깔려 있어야 한다.
컴포넌트 레벨의 품질을 확보했다면, 다음 질문은 PR 단위의 검증이다. 여기서 'AI 코드 리뷰를 AI가 승인했다'는 구조의 맹점이 드러난다. 그린 체크마크는 프로세스가 실행됐다는 사실만 증명한다. 무엇을 봤는지, 무엇을 포기했는지, 어떤 논쟁이 있었는지는 남지 않는다. AI 리뷰 도구가 생성한 수많은 발견 사항들은 채팅창 안에서 해소되거나 기각되고, 그 맥락 전체가 사라진 채 체크마크만 남는다.
dev.to에서 소개된 Disensor는 이 문제를 '통제된 불일치(controlled disagreement)' 방식으로 접근한다. 한 AI가 코드를 생성하면, 다른 모델 패밀리의 AI가 명시적 적대적 프롬프트로 공격한다. 생성 AI는 모든 발견 사항에 답해야 하고, 각 사항이 반영·반박·에스컬레이션 중 하나로 처리될 때까지 사이클이 끝나지 않는다. 발견 사항이 그냥 사라지는 것을 허용하지 않는다는 원칙이 핵심이다.
이 사이클의 결과물은 '잔여 선언(residue declaration)' 이라는 JSON 아티팩트로 기록된다. 누가 생성했는지, 누가 공격했는지(모델 패밀리와 프롬프트 해시), 각 발견 사항의 최종 상태, 그리고 사이클이 스스로 해결하지 못하고 사람의 판단에 넘긴 잔여 항목까지. Disensor는 이 아티팩트를 GitHub Action으로 CI 게이트에 연결한다. PR이 코드를 건드리면 반드시 유효한 선언을 달아야 하고, 선언은 정확히 머지되는 트리를 커버해야 하며, 증거는 추가만 가능하고 삭제할 수 없다. '리뷰됨, 이상 없음' 같은 제네릭 마커는 자동으로 거부된다.
52일간 5개 프로젝트에 걸쳐 91건의 리뷰 이벤트를 기록한 결과, 발견 사항의 64.9%가 반영되고 3.6%가 증거 기반으로 반박됐다. 도구 자체도 같은 게이트를 통과하도록 설계돼 있어, .residue/ 디렉터리에 Disensor 자체 개발 과정의 실제 선언들이 쌓여 있다. 한 가지 한계는 솔직하게 명시돼 있다. 도구는 빈 필드와 제네릭 마커를 감지할 수 있지만, 거짓 선언은 감지할 수 없다. 모델이나 사람이 아티팩트에 거짓을 적으면, 머지된 PR을 사람이 샘플링하는 수밖에 없다. 이 경계를 인정하고 설계하는 것이 오히려 신뢰할 수 있는 구조를 만드는 방식이다.
세 번째 레이어는 데이터 레벨의 표기 검증이다. 실시간 마켓 데이터를 보여주는 UI를 만든 국내 개발자의 작업 로그(SlateKR #115, velog)는 이 문제를 가장 실무적인 형태로 보여준다. '국내 정규장 마감'이라는 같은 상태가 화면마다 세 가지 다른 문구로 표기되고 있었다. 홈 슬레이트는 15:30 장 마감 · 기준일, 지수 상세는 정규장 마감 · YYYY-MM-DD 15:30, 종목 헤더는 HH:mm:ss · [dot] 장 마감. '실시간' dot의 위치, 15분 지연 표현 방식도 제각각이었다.
문법을 [dot] 상태문구 · 기준시각 하나로 통일하는 작업은 표기 정규화처럼 보이지만, 실제로는 측정 다섯 번짜리 검증 세션이었다. '~15분 지연'이라고 표기된 다우 지수가 실제로는 전일 에코였고, 나머지 해외 지수들은 지연 여부 자체를 측정한 적이 없었다. 니케이와 DAX는 실측 결과 15.4~15.5분 지연이 확인됐고, S&P500·나스닥 계열은 0.2~0.3분으로 실시간이었다. 미국 지수를 '그냥 15분으로 치자'는 생각을 잠깐 했다가, 밤에 직접 재고 나서 실시간임을 확인했다. 하루 종일 무실측 단정을 걷어내다가 마지막에 하나 심을 뻔한 순간이었다.
이 작업이 남긴 문장이 있다. "라벨은 통일이 아니라 검증의 문제였던 셈이다." 표기 규칙을 하나로 맞추는 것은 시작일 뿐이고, 그 표기가 실제로 참인지—'실시간'이라 쓰려면 실시간인지 알아야 하고, '지연'이라 쓰려면 몇 분인지 알아야 한다는 원칙—이 없으면 문법 통일은 일관된 거짓말을 만드는 데 그친다.
세 가지 사례를 겹쳐보면 하나의 패턴이 보인다. 컴포넌트 레벨에서는 검증된 바닥 없이 AI 생성을 반복하면 접근성 버그가 누적된다. PR 레벨에서는 AI 리뷰의 맥락이 체크마크 뒤로 사라지면 무엇이 의도적으로 넘어갔는지 추적할 수 없다. 데이터 표현 레벨에서는 측정 없이 단정한 표기가 일관성 있게 틀린 UI를 만든다. 바이브 코딩이 가져다주는 속도는 진짜다. 하지만 그 속도가 품질로 연결되려면, 각 레이어에서 무엇을 어떻게 검증할지를 미리 설계한 사람이 있어야 한다. AI는 그 설계를 대신해주지 않는다.