바이브 코딩 결과물을 인수·검증·복구·운영하는 실전 안내서
결함이 있는 예약 SaaS RoomRelay를 인수해 재현, 특성 테스트, 인증, 시간, 중복 요청, 장애 복구와 인계 가능한 릴리스까지 완주한다.
상태: 출간 후보 웹교정쇄 · 베타 리딩 대기 · 15개 장

AI가 몇 분 만에 화면을 만들었다. 버튼도 눌리고 배포 주소도 생겼다. 그런데 예약이 겹치고, 새로 고침하면 로그인이 풀리고, 오류가 나면 무엇을 봐야 할지 모른다. 이때 필요한 능력은 프롬프트를 더 길게 쓰는 기술이 아니다. 현재 상태를 증거로 남기고, 한 번에 하나씩 바꾸고, 바뀐 결과를 확인하는 기술이다.
이 책에서는 일부러 결함을 남긴 공간 예약 서비스 RoomRelay를 인수한다. 사용자는 공간과 시간을 골라 예약을 요청하고, 운영자는 보류된 요청을 확인해 확정한다. 결제는 흉내만 내며 실제 돈은 움직이지 않는다. 책을 끝내면 독자는 다음을 할 수 있다.
- 실행되지 않는 프로젝트에서 첫 오류를 분리한다.
- AI에게 맡길 변경의 목표·제약·완료 조건을 쓴다.
- 현재 동작을 특성 테스트로 잠근다.
- 인증, 시간, 중복 요청, 겹침 예약을 차례로 복구한다.
- 로그·지표·롤백을 갖춘 작은 배포를 만든다.
- “AI가 고쳤다”가 아니라 “검증 증거가 있다”고 말한다.
이 책의 예제는 교육용 가상 데이터다. 실제 결제·문자·메일을 보내지 않는다.
- 코드를 고치기 전에 인수한다
- 재현 가능한 첫 실행을 만든다
- 사용자 여정으로 시스템 지도를 그린다
- 현재 동작을 특성 테스트로 잠근다
- 인증과 권한의 경계를 세운다
- 데이터 규칙을 코드 밖에서도 지킨다
- 날짜와 시간대의 함정을 제거한다
- 결제와 웹훅을 안전하게 흉내 낸다
- 중복 요청과 동시성을 견딘다
- 비밀·의존성·입력을 점검한다
- 화면의 실패 경험과 접근성을 고친다
- 로그·지표·추적으로 원인을 찾는다
- Codex와 검증 계약을 맺는다
- 장애 훈련과 롤백을 실시한다
- 인수 가능한 릴리스를 만든다
1장 코드를 고치기 전에 인수한다
고장 난 프로젝트를 받으면 바로 파일을 수정하고 싶어진다. 첫날의 목표는 수정이 아니라 경계 확정이다. 무엇이 제품이고, 어떤 데이터가 들어가며, 어떤 외부 행동이 가능한지 모르면 좋은 수정과 위험한 수정도 구분할 수 없다.
먼저 저장소의 사실을 한 장에 적는다.
- 시작 명령과 필요한 런타임 버전
- 사용자 역할과 핵심 여정
- 데이터 저장 위치와 초기화 방법
- 외부 서비스와 실제 비용이 발생하는 행동
- 이미 알려진 오류와 아직 확인하지 못한 영역
- 성공을 판단할 테스트·화면·로그
RoomRelay의 첫 인수 메모는 간단하다. Node.js로 실행되며 공간 예약을 메모리에 저장한다. start/booking.mjs는 시작 시각이 같은 예약만 중복으로 판단한다. 10시부터 11시 예약이 있어도 10시 30분부터 11시 30분 요청은 통과한다. ID가 난수라 동일 요청을 재전송하면 예약이 두 개 생긴다. 요청과 확정도 한 단계라 운영자 확인이 없다.
중요한 것은 결함의 개수가 아니다. 독자가 직접 재현할 수 있는 문장으로 바꾸는 것이다.
조건: room-a에 10:00~11:00 예약이 존재한다.
행동: 다른 사용자가 10:30~11:30을 요청한다.
기대: OVERLAP으로 거부한다.
현재: 새 confirmed 예약을 만든다.
이 문장이 첫 테스트가 된다. “예약이 가끔 겹쳐요”라는 보고는 고칠 수 없지만, 입력과 기대가 있는 보고는 코드와 대조할 수 있다.
첫날에 하지 않을 일
패키지를 전부 최신 버전으로 올리지 않는다. 화면을 새로 디자인하지 않는다. 데이터베이스를 교체하지 않는다. AI에게 “전체적으로 리팩터링해 줘”라고 하지 않는다. 이 작업은 원인과 변경을 섞어 비교를 어렵게 만든다. 첫날에는 실행, 재현, 범위, 안전장치만 만든다.
장 실습
examples/roomrelay/start/booking.mjs를 읽고 세 가지 결함을 재현 문장으로 적는다. 아직 고치지 않는다. 문장마다 조건, 행동, 기대, 현재 결과가 있는지 확인한다.
2장 재현 가능한 첫 실행을 만든다
“제 컴퓨터에서는 됩니다”는 환경 설명이 빠진 문장이다. 복구 가능한 프로젝트는 새 폴더에서도 같은 명령으로 같은 결과를 낸다. 런타임 버전, 설치 명령, 환경 변수 이름, 시작 명령을 README에 고정한다.
첫 실행은 세 단계로 나눈다.
- 구문과 의존성: 프로세스가 시작되는가.
- 준비 상태: 데이터 저장소와 필수 설정이 준비됐는가.
- 핵심 여정: 예약 보류와 운영자 확인이 이어지는가.
오류가 나면 긴 로그 전체를 AI에 던지기보다 첫 원인과 환경을 함께 준다.
목표: npm test가 새 체크아웃에서 실행되어야 한다.
환경: Node 24, macOS, API 키 없음.
관찰: 첫 실패는 ERR_MODULE_NOT_FOUND이며 경로는 .../booking.mjs다.
제약: 새 의존성을 추가하지 않는다. 라이브 결제를 호출하지 않는다.
완료: 원인을 설명하고 최소 수정 후 전체 테스트가 통과한다.
종료 코드는 중요한 증거다. 화면에 “완료”가 보이더라도 프로세스가 1로 끝났다면 자동화에서는 실패다. 반대로 경고 한 줄이 있어도 종료 코드가 0이고 기대 결과가 맞을 수 있다. 로그 문구, 종료 코드, 생성 파일을 함께 본다.
환경 변수 표
비밀값 자체가 아니라 이름·필수 여부·실패 방식을 문서화한다.
| 이름 | 기본 실습 | 라이브 환경 | 없을 때 |
|---|---|---|---|
PORT |
선택 | 선택 | 4300 사용 |
DATABASE_URL |
사용 안 함 | 필수 | 시작 중단 |
PAYMENT_KEY |
사용 안 함 | 선택 실습 | 결제 어댑터 비활성 |
3장 사용자 여정으로 시스템 지도를 그린다
파일 트리만 보면 중요한 경계를 놓친다. 사용자가 하는 행동을 따라가며 입력, 판단, 저장, 외부 행동을 표시한다.
RoomRelay의 핵심 여정은 다음과 같다.
공간 선택 → 시간 입력 → 겹침 검사 → 예약 보류
→ 운영자 검토 → 확정 → 알림 초안 → 발송 승인
각 화살표에 실패 질문을 붙인다. 시간이 잘못됐으면 어디서 멈추는가? 사용자가 버튼을 두 번 누르면 무엇이 중복되는가? 운영자가 확인하는 동안 다른 사용자가 같은 시간을 잡으면 누가 승리하는가? 알림 전송이 실패해도 예약은 확정 상태인가?
여정 지도에서 위험도가 높은 경계는 색을 달리한다.
- 인증 전후
- 데이터 쓰기 전후
- 돈·메일·문자처럼 되돌리기 어려운 행동 전후
- AI가 만든 제안과 사람이 한 약속 사이
이 지도는 폴더 구조가 바뀌어도 유지된다. AI에게 작업을 맡길 때도 “어느 경계의 어떤 실패를 바꾸는가”를 지정할 수 있다.

그림에서 왼쪽은 요청 fixture, 오른쪽은 선택한 요청의 도메인 판정과 사람 확인 항목이다. 독자는 held를 confirmed로 오해하지 않도록 상태 문구와 버튼을 함께 확인한다. 책의 테스트와 화면은 모두 REQ-2401, requestId req-1이라는 같은 fixture를 사용한다.
4장 현재 동작을 특성 테스트로 잠근다
레거시 코드에는 명세가 없을 수 있다. 이때 현재 동작을 관찰해 테스트로 기록하는 방법이 특성 테스트다. 현재 동작이 옳다는 선언이 아니라, 변경 전후 차이를 알아채는 경보다.
먼저 정상 예시 하나와 위험 예시 하나를 고정한다.
test('겹치는 예약은 거부한다',()=>{
const rows=[];
reserve(first,rows,{now});
const result=reserve(overlap,rows,{now});
assert.equal(result.error,'OVERLAP');
});
테스트 이름에는 구현이 아니라 약속을 쓴다. “filter가 true를 반환한다”보다 “겹치는 예약은 거부한다”가 오래간다. 입력 시간과 현재 시각은 고정한다. 테스트에서 new Date()를 직접 부르면 자정, 시간대, 실행 날짜에 따라 결과가 달라진다.
테스트가 실패했을 때 바로 기대값을 바꾸지 않는다. 제품 규칙이 바뀐 것인지, 구현이 깨진 것인지 먼저 판단한다. AI가 테스트와 구현을 동시에 바꾸면 잘못된 동작을 새 정답으로 만들기 쉽다.
5장 인증과 권한의 경계를 세운다
로그인 성공과 권한 부여는 다른 문제다. 사용자가 누구인지 확인한 뒤에도 그 사용자가 특정 예약을 볼 수 있는지 별도로 판단해야 한다.
권한 검사는 UI에서 버튼을 숨기는 것으로 끝나지 않는다. 서버의 쓰기 경계에서 다시 확인한다.
사용자: 자기 예약 생성·조회·취소 요청
운영자: 담당 공간의 보류 예약 확인·확정
관리자: 사용자·공간 정책 변경
세션 쿠키에는 HttpOnly, Secure, 적절한 SameSite 정책을 검토한다. 로그에는 토큰과 쿠키를 남기지 않는다. “관리자 화면 주소를 모르면 안전하다”는 가정도 버린다. 모든 민감 작업은 인증과 권한 검사를 함께 통과해야 한다.
6장 데이터 규칙을 코드 밖에서도 지킨다
애플리케이션 검사만으로 동시 요청을 완전히 막기 어렵다. 가능한 규칙은 데이터베이스 제약으로 한 번 더 지킨다. 사용자 이메일의 고유성, 요청 ID의 고유성, 외래키, 상태 값 범위가 대표적이다.
마이그레이션은 앞으로 가는 스크립트만 준비하지 않는다. 적용 전 백업, 소요 시간, 잠금 영향, 되돌리는 절차를 함께 기록한다. 큰 테이블에 NOT NULL 열을 바로 추가하면 서비스가 멈출 수 있다. nullable 열 추가, 데이터 채우기, 검증, 제약 강화처럼 나눈다.
RoomRelay의 메모리 예제에서는 requestId를 고유한 멱등성 키로 사용한다. 실제 DB에서는 UNIQUE(request_id)와 트랜잭션을 결합해야 한다. 책의 배열 구현은 개념 학습용이지 운영용 동시성 제어가 아니다.
7장 날짜와 시간대의 함정을 제거한다
예약 서비스에서 “10시”는 불완전한 값이다. 날짜, 시간대, 일광절약시간 여부가 필요하다. 저장은 UTC 인스턴트로 통일하고, 사용자에게 보여 줄 때 지역 시간대로 변환하는 방법이 단순하다. 다만 매주 월요일 10시처럼 지역 시각 자체가 규칙인 일정은 시간대 이름도 보존한다.
비교 전에 입력이 유효한지 확인한다. 시작은 종료보다 빨라야 하고, 과거 예약 정책을 적용해야 하며, 경계가 맞닿은 두 예약을 겹침으로 볼지 정해야 한다. 이 책은 [start, end) 구간을 사용한다. 10시~11시 예약과 11시~12시 예약은 겹치지 않는다.
const overlaps = start < existingEnd && end > existingStart;
이 한 줄보다 중요한 것은 구간 규칙을 테스트 이름과 사용자 문구에 함께 남기는 일이다.
8장 결제와 웹훅을 안전하게 흉내 낸다
결제는 기본 실습에서 절대 라이브로 연결하지 않는다. 먼저 PaymentPort라는 작은 인터페이스를 만들고 성공, 거절, 지연, 중복 웹훅을 fixture로 재생한다.
웹훅은 네트워크가 응답을 잃으면 같은 이벤트를 다시 보낸다. 이벤트 ID를 저장하고 이미 처리한 ID는 같은 성공 응답을 돌려준다. 서명 검증은 원본 바이트로 수행하며 파싱 후 다시 만든 JSON을 사용하지 않는다.
결제 승인과 예약 확정이 한 트랜잭션이 될 수 없다면 상태를 분리한다. payment_pending, paid, confirmation_pending, confirmed, refund_required처럼 복구 가능한 중간 상태가 필요하다. “에러면 처음부터 다시”는 돈이 움직인 뒤에는 안전하지 않다.
9장 중복 요청과 동시성을 견딘다
사용자는 버튼을 두 번 누른다. 모바일 네트워크는 응답을 잃는다. 프록시는 재시도한다. 중복은 예외가 아니라 정상 환경이다.
클라이언트가 요청 ID를 만들고 서버가 그 ID와 결과를 보존하면 같은 요청을 재생해도 같은 예약을 돌려줄 수 있다. 멱등성 키는 payload와 함께 묶어야 한다. 같은 키로 다른 예약 시간을 보내면 충돌로 거부한다.
겹침 검사와 쓰기 사이에도 경쟁이 있다. 두 요청이 동시에 “비어 있음”을 보고 둘 다 저장할 수 있다. 실제 구현에서는 직렬화 가능한 트랜잭션, 잠금, 범위 제약 또는 예약 슬롯 모델 가운데 데이터베이스에 맞는 방법을 택한다. 배열 테스트를 통과했다고 운영 동시성이 해결됐다고 쓰지 않는다.
10장 비밀·의존성·입력을 점검한다
저장소에서 .env, 개인 키, 접속 문자열을 검색한다. 발견하면 파일만 지우지 말고 비밀을 폐기하고 다시 발급한다. Git 기록에 남았는지도 확인한다. 예제 값은 example.invalid 같은 예약 도메인과 명백한 가짜 토큰을 쓴다.
의존성 업데이트는 목적별로 나눈다. 보안 수정, 런타임 호환, 기능 추가를 한 커밋에 섞지 않는다. 잠금 파일을 보존하고 설치 스크립트가 임의 코드를 실행하는지 확인한다.
입력은 길이, 형식, 허용 값, 권한을 서버에서 검사한다. 오류 메시지에는 내부 스택이나 SQL을 노출하지 않는다. 로그에는 이메일·전화번호·토큰을 마스킹한다.
11장 화면의 실패 경험과 접근성을 고친다
정상 화면만 캡처하면 서비스의 절반만 본다. 로딩, 빈 목록, 권한 없음, 시간 충돌, 서버 오류, 재시도 중, 완료 상태를 각각 확인한다.
예약 실패 문구는 다음 행동을 알려야 한다.
나쁨: 오류가 발생했습니다.
좋음: 선택한 시간에 다른 예약이 생겼습니다. 목록을 새로 확인하고 다른 시간을 골라 주세요.
버튼은 키보드로 이동하고 실행할 수 있어야 한다. 입력에는 보이는 레이블이 필요하다. 색만으로 예약 상태를 구분하지 않는다. 포커스가 사라지지 않는지, 오류 요약이 스크린리더에 전달되는지 확인한다.
12장 로그·지표·추적으로 원인을 찾는다
좋은 로그는 사건을 재구성한다. 요청 ID, 사용자 대신 비식별 주체 ID, 동작, 결과, 지연 시간, 오류 코드를 구조화해 남긴다. 비밀번호와 토큰, 예약 메모 원문은 남기지 않는다.
지표는 질문과 연결한다.
- 예약 요청 중 충돌 비율은 얼마인가.
- 보류에서 확정까지 얼마나 걸리는가.
- 같은 요청 ID 재생은 얼마나 발생하는가.
- 알림 초안 실패가 예약 확정에 영향을 주는가.
AI가 원인을 요약할 수 있지만 관찰 데이터가 빈약하면 그럴듯한 이야기를 만들 뿐이다. 먼저 로그와 지표를 수집하고, AI 요약에는 근거 이벤트 링크를 요구한다.
13장 Codex와 검증 계약을 맺는다
Codex에게는 목표, 관련 파일, 제약, 완료 조건을 준다. 저장소의 반복 규칙은 AGENTS.md에 둔다.
# AGENTS.md
- 예약 도메인 변경 뒤 `npm test`를 실행한다.
- 라이브 결제·메일·문자를 호출하지 않는다.
- 시간 테스트에는 고정된 `now`를 주입한다.
- 완료 보고에는 바뀐 파일과 실행한 검증을 적는다.
AGENTS.md는 마법의 품질 보증서가 아니다. 실제 명령이 맞고 빠르게 실행되어야 한다. 규칙이 길어지면 반복 실수와 관계없는 설명을 덜어 낸다.
좋은 작업 요청은 작다.
RoomRelay의 겹침 예약만 수정해 줘.
현재 [start,end) 규칙을 유지하고 시간대 변환은 건드리지 마.
실패 테스트를 먼저 추가한 뒤 최소 구현으로 통과시켜.
전체 테스트와 변경 diff를 검토하고 남은 위험을 보고해.
AI가 구현과 테스트를 모두 작성했으면 독자는 실패 테스트가 수정 전 코드에서 실제로 실패했는지 확인한다. 이것이 “테스트도 있습니다”와 회귀를 잡는 테스트의 차이다.
14장 장애 훈련과 롤백을 실시한다
운영에서 처음 롤백을 시도하면 늦다. 배포 전에 세 가지 장애를 연습한다.
- 새 버전의 예약 생성 오류율이 급증한다.
- DB 마이그레이션 뒤 이전 버전이 새 열을 이해하지 못한다.
- 알림 공급자가 느려져 요청이 쌓인다.
각 상황에 감지 신호, 중단 기준, 책임자, 롤백 명령, 데이터 확인, 사용자 안내를 적는다. 롤백은 코드 버전만 되돌리는 작업이 아니다. 새 형식으로 저장된 데이터와 이미 실행된 외부 행동을 처리해야 한다.

이 화면은 장식용 대시보드가 아니다. 수정 전 실패, 수정 범위, 전체 테스트와 외부 쓰기 수, 장애 중단 조건을 릴리스 인계에 그대로 옮길 수 있게 구성했다.
15장 인수 가능한 릴리스를 만든다
릴리스 후보는 “제 컴퓨터에서 됨”을 넘어 다른 사람이 인수할 수 있어야 한다.
- 새 체크아웃 설치와 시작 명령
- 환경 변수 목록과 비밀 관리 방법
- 핵심 여정 테스트와 실패 상태 캡처
- DB 변경·백업·복구 절차
- 로그와 대시보드 위치
- 알려진 제한과 다음 작업
- 롤백 조건과 명령
최종 데모에서는 예약 하나를 보류하고 운영자가 확정한다. 같은 요청을 다시 보내도 새 예약이 생기지 않는지 확인한다. 겹치는 시간을 보내 OVERLAP을 본다. 로그에서 요청 ID로 전체 흐름을 찾는다. 마지막으로 새 담당자가 README만 보고 같은 결과를 내는지 확인한다.
완주 체크
cd examples/roomrelay
node --test test/*.test.mjs
세 테스트가 통과하는 것만으로 운영 준비가 끝나지는 않는다. 그러나 독자는 이제 무엇이 빠졌는지 설명할 수 있다. 메모리 배열 대신 DB 트랜잭션이 필요하고, 실제 인증과 부하 테스트가 필요하며, 결제 연결 전 법률·보안 검토가 필요하다는 경계를 안다. 그 경계를 아는 것이 코드 구조대의 출발이자 완주다.
실습 워크북: RoomRelay를 직접 인수하는 15개 미션
이 워크북은 앞의 15장을 같은 프로젝트에서 다시 실행한다. 정답 파일을 먼저 복사하지 않는다. 각 미션의 “완료 증거”를 자기 작업 기록에 남긴다.
미션 1 저장소 인수 봉투 만들기
rescue-notes/00-intake.md를 만들고 런타임, 시작 명령, 외부 연결, 데이터 초기화, 원본 보존 위치를 기록한다. start/booking.mjs의 결함을 아직 고치지 않는다.
완료 증거는 다섯 항목이 채워진 문서와 원본 파일 해시다. 비밀값은 기록하지 않는다. 외부 서비스가 무엇인지 모르면 “없음”이 아니라 “미확인”이라고 쓴다.
미션 2 첫 실패를 한 명령으로 재현하기
Node REPL 또는 작은 스크립트에서 10시~11시 예약 뒤 10시 30분~11시 30분 예약을 넣는다. 두 번째 요청이 통과하는 결과를 rescue-notes/01-overlap.txt에 저장한다.
const rows=[];
reserve(first,rows);
console.log(reserve(partialOverlap,rows));
완료 증거는 조건·행동·기대·현재 결과 네 문장이다. “중복 오류”만 적으면 다시 보완한다.
미션 3 사용자 여정에 쓰기 경계 표시하기
예약 선택부터 알림 초안까지의 흐름을 그린다. 데이터 저장과 외부 전송 앞에 WRITE를, 사람이 약속을 승인하는 곳에 HUMAN GATE를 붙인다. 화면 버튼과 서버 함수가 같은 경계를 가리키는지 대조한다.
미션 4 수정 전에 실패 테스트 만들기
부분 겹침 테스트를 추가하고 수정 전 코드에 실행한다. 테스트가 녹색이면 잘못된 구현을 호출했거나 입력이 실제로 겹치지 않는 것이다. 수정 전 실패 출력과 수정 뒤 성공 출력을 둘 다 보존한다.
node --test examples/roomrelay/test/*.test.mjs
책의 solution을 가져오기 전에 자기 테스트가 같은 규칙을 표현하는지 확인한다.
미션 5 권한 표를 서버 검사로 바꾸기
사용자, 운영자, 관리자의 허용 동작을 표로 만든 뒤 confirm에 승인자 요구가 있는지 확인한다. 빈 승인자로 호출했을 때 상태가 변하지 않아야 한다. 실패 응답 뒤 배열의 예약 상태도 검사한다. 응답만 오류고 데이터는 이미 바뀌는 부분 실패를 놓치지 않는다.
미션 6 DB 제약 설계하기
코드를 DB에 연결하지 않고도 필요한 제약을 SQL 초안으로 적는다.
create unique index booking_request_id_uq on booking(request_id);
alter table booking add constraint booking_status_ck
check (status in ('held','confirmed','cancelled'));
이 SQL을 그대로 운영에 적용하지 않는다. 기존 중복 데이터 조사, 잠금 영향, 되돌리기, DB별 겹침 제약 방법을 작업 계획에 추가해야 완료다.
미션 7 시간 경계표 완성하기
다섯 사례를 표로 만든다: 완전 분리, 끝과 시작이 맞닿음, 부분 겹침, 기존 예약 안에 포함, 기존 예약을 완전히 포함. [start,end) 규칙에서 기대 결과를 먼저 쓰고 테스트한다. 잘못된 날짜와 시작이 종료보다 늦은 사례도 추가한다.
미션 8 결제 웹훅 fixture 설계하기
실제 결제 API 대신 네 JSON 파일을 만든다: 성공, 거절, 동일 이벤트 재전송, 서명 오류. 이벤트 ID와 원본 바이트 해시를 기록하고 중복 이벤트가 새 예약을 만들지 않는 기대를 쓴다. 라이브 키와 실제 카드 번호를 사용하면 실습 실패다.
미션 9 requestId 재생 공격하기
같은 요청을 열 번 호출한다. 배열 길이는 1이고 ID도 같아야 한다. 같은 requestId에 다른 시간 payload를 보내는 사례를 설계한다. 현재 교육용 구현은 payload 충돌 검증이 충분하지 않다는 제한을 발견 기록에 남긴다.
for(let i=0;i<10;i++) reserve(request,rows,{now});
assert.equal(rows.length,1);
미션 10 비밀과 개인정보 탐색하기
rg로 토큰 형태, .env, 이메일, 전화번호를 검색한다. 검색 결과를 그대로 보고서에 붙이지 않는다. 파일·줄 위치와 비밀 종류만 기록하고 실제 값은 마스킹한다. 이미 커밋된 비밀은 삭제만 하지 말고 폐기·재발급 계획을 세운다.
미션 11 실패 화면 세 가지 만들기
RoomRelay 화면에 시간 충돌, 권한 없음, 서버 지연 문구를 작성한다. 각 문구는 무엇이 일어났는지, 데이터가 저장됐는지, 사용자가 다음에 할 일을 알려야 한다. 색을 제거한 화면에서도 상태를 구분할 수 있는지 본다.
미션 12 요청 ID로 사건 재구성하기
가상 로그 다섯 줄을 작성한다: 요청 수신, 검증, 겹침 검사, 보류 저장, 승인. 모든 줄에 같은 requestId를 넣고 개인정보 원문은 빼며 오류 코드는 안정적으로 유지한다. 이 로그만으로 처리 순서와 마지막 성공 지점을 찾을 수 있어야 한다.
미션 13 Codex 작업 계약 실행하기
저장소 루트에 짧은 AGENTS.md를 만들고 테스트 명령, 라이브 외부 행동 금지, 시간 주입, 완료 보고 형식을 적는다. Codex에게 한 결함만 수정하도록 요청한 뒤 diff와 테스트 출력을 직접 본다. 규칙을 읽었다는 답변보다 실제 변경이 규칙을 지켰는지가 증거다.
미션 14 10분 장애 훈련하기
“새 릴리스 뒤 예약 오류율 7%”라는 상황을 주고 타이머를 시작한다. 감지, 쓰기 중단, 릴리스 ID 확인, 이전 버전 전환, 보류 데이터 대조, 사용자 공지 초안을 10분 안에 완성한다. 실제 운영 명령은 실행하지 않고 runbook으로만 연습한다.
미션 15 다른 사람에게 인계하기
새 폴더에서 README만 보고 테스트를 실행할 베타 독자를 정한다. 설명을 추가로 말해 주지 않는다. 독자가 막힌 지점, 잘못 이해한 상태, 실행하지 못한 명령을 기록한다. 그 피드백으로 README와 장의 순서를 고친다.
워크북 최종 채점표
| 항목 | 미달 | 통과 |
|---|---|---|
| 재현 | 증상 설명만 있음 | 입력·기대·현재 결과가 있음 |
| 테스트 | 수정 뒤에만 녹색 | 수정 전 실패와 수정 뒤 성공 확인 |
| 상태 | 화면 문구만 분리 | 서버 도메인에서 held/confirmed 분리 |
| 중복 | 버튼 비활성화 | 서버 requestId와 저장 제약 설계 |
| 시간 | 로컬 문자열 비교 | UTC 인스턴트·지역 표시·경계 테스트 |
| 외부 행동 | 샌드박스 호출 | 기본 fixture, 라이브는 별도 승인 |
| 인계 | “테스트 통과” 보고 | 변경·검증·한계·롤백이 함께 있음 |
일곱 항목이 모두 통과해도 운영 보안 승인은 별도다. 이 채점표는 독자가 책의 기술을 수행했는지 확인하는 학습 기준이다.
부록 A 증상별 첫 질문
| 증상 | 먼저 확인할 것 | 피할 행동 |
|---|---|---|
| 설치 실패 | 런타임·잠금 파일·첫 오류 | 패키지 전체 업그레이드 |
| 로그인 풀림 | 쿠키 속성·프록시·세션 저장소 | 인증 라이브러리 즉시 교체 |
| 예약 중복 | 요청 ID·DB 제약·재시도 | 버튼만 비활성화 |
| 시간 어긋남 | 입력 시간대·저장 형식·표시 지역 | 문자열 잘라 맞추기 |
| 간헐 오류 | 요청 ID별 로그·지연·의존 서비스 | 재현 없이 타임아웃 확대 |
부록 B 출간 전 다시 확인할 자료
- Codex 사용법과
AGENTS.md탐색 규칙은 출간 직전 OpenAI 공식 문서를 다시 확인한다. - 보안 헤더와 쿠키 속성은 배포 환경과 최신 브라우저 기준으로 검토한다.
- 결제 예제는 특정 사업자의 문구를 복제하지 않고 일반적인 포트·어댑터 구조만 사용한다.
부록 C 90분 코드 구조대 워크숍
0~15분: 저장소를 보존한다
현재 브랜치, 마지막 커밋, 변경 파일을 기록한다. Git 저장소가 아니라면 원본 폴더를 읽기 전용 사본으로 보존한다. 운영 데이터베이스에 연결된 환경 변수는 제거하고 fixture 환경에서 시작한다. npm install 같은 명령도 설치 스크립트를 실행할 수 있으므로 package.json과 잠금 파일을 먼저 본다.
산출물은 긴 보고서가 아니다.
원본 위치:
현재 커밋:
실행한 명령:
외부 연결 차단 상태:
되돌리는 방법:
15~30분: 첫 실패를 고정한다
한 번에 명령 하나를 실행하고 표준 출력, 표준 오류, 종료 코드를 보존한다. 두 번째 오류를 고치기 전에 첫 오류가 뒤의 오류를 만든 것은 아닌지 확인한다. 브라우저 문제라면 URL, 화면 크기, 로그인 상태, 클릭 순서, 콘솔의 첫 오류를 함께 적는다.
30~50분: 최소 재현을 만든다
전체 서비스를 띄우지 않고도 도메인 함수를 호출할 수 있다면 작은 테스트로 줄인다. 네트워크가 필요하다면 응답 fixture를 저장하되 토큰과 개인정보를 지운다. 최소 재현은 실제 문제와 같은 이유로 실패해야 한다. 단순히 빨간 테스트를 만드는 것이 목적이 아니다.
50~70분: 한 가설만 수정한다
수정 전 가설을 문장으로 쓴다. 예를 들어 “겹침 검사가 시작 시각의 동일성만 비교하기 때문에 부분 중복을 허용한다.” 수정 뒤에는 해당 테스트만 먼저 실행하고, 통과하면 전체 테스트를 실행한다. 포매터가 전체 파일을 바꿔 diff를 가리지 않도록 한다.
70~90분: 반증하고 인계한다
정상 사례만 확인하지 않는다. 경계가 맞닿는 예약, 완전히 포함되는 예약, 과거 시각, 잘못된 날짜, 같은 요청 재생을 시도한다. 마지막에는 다음 형식으로 인계한다.
수정한 증상:
근본 원인:
바꾼 파일:
수정 전 실패 증거:
수정 뒤 검증:
검증하지 못한 영역:
롤백 방법:
부록 D AI에게 맡길 때 쓰는 네 가지 프롬프트
진단 프롬프트
아직 파일을 수정하지 마. 재현 명령과 첫 실패를 기준으로 가능한 원인을
증거가 강한 순서로 세 개만 제시해. 각 원인을 확인할 읽기 전용 명령과,
그 결과가 가설을 지지하거나 반박하는 조건을 적어 줘.
특성 테스트 프롬프트
현재 사용자에게 보이는 동작을 바꾸지 않고 특성 테스트를 추가해.
시간과 난수는 주입하고 네트워크는 fixture로 대체해.
테스트마다 왜 필요한지 한 줄로 설명하되 구현 세부를 테스트 이름에 넣지 마.
최소 수정 프롬프트
실패 테스트 한 개를 통과시키는 최소 변경만 해.
공개 API, DB 스키마, 의존성 버전은 바꾸지 마.
수정 후 대상 테스트와 전체 테스트를 실행하고 diff에서 예상 밖 변경을 찾아.
네거티브 리뷰 프롬프트
이 변경이 틀렸다고 가정하고 검토해. 인증 우회, 중복 요청, 시간대 경계,
부분 실패, 로그의 개인정보, 롤백 불가능성을 재현 가능한 사례로만 보고해.
근거 없는 일반론과 스타일 의견은 제외해.
AI가 “완료했다”고 말해도 명령 출력과 파일 diff를 직접 확인한다. 도구에 파일 쓰기 권한이 없었거나 테스트가 다른 폴더에서 실행됐을 수 있다.
부록 E 증상별 상세 복구 지도
새로 고침하면 상태가 사라진다
브라우저 메모리에만 있는지, 서버 세션인지, DB에 저장되는지 경계를 추적한다. 쿠키가 보안 속성 때문에 로컬 HTTP에서 전송되지 않는지, 프록시가 호스트와 프로토콜 헤더를 바르게 전달하는지 확인한다. 상태를 급히 localStorage로 옮기면 민감 정보와 동기화 문제가 생길 수 있다.
같은 버튼에서 두 레코드가 생긴다
UI의 이중 클릭, 네트워크 재시도, 서버 타임아웃 뒤 재요청, 작업 큐 재배달을 구분한다. 버튼 비활성화는 사용 경험을 돕지만 서버 멱등성을 대신하지 않는다. 요청 ID, 고유 제약, 처리 결과 저장을 함께 설계한다.
배포는 성공했는데 이전 화면이 보인다
빌드 산출물의 해시, 배포 대상 경로, CDN·브라우저 캐시, 서비스 워커, 여러 인스턴스의 버전을 확인한다. 서버 한 대만 새 버전이면 요청마다 화면이 달라질 수 있다. 화면 하단 또는 응답 헤더에 릴리스 ID를 노출하면 원인을 줄일 수 있다.
오류가 재현되지 않는다
재현되지 않는다고 해결된 것은 아니다. 발생 시각의 요청 ID, 사용자 역할, 입력 범주, 배포 버전, 지역, 브라우저, 의존 서비스 상태를 모은다. 개인정보 원문 대신 필요한 범주와 비식별 식별자를 쓴다. 관측 정보가 없다면 먼저 안전한 로깅을 추가하고 기다리는 것이 무작정 수정하는 것보다 낫다.
테스트는 통과하지만 운영에서 실패한다
테스트가 다루지 않은 경계를 찾는다. 실제 프록시, DB 격리 수준, 시간대 데이터, 파일 권한, 네트워크 제한, 데이터 규모가 대표적이다. 운영 장애를 그대로 복제할 수 없다면 가장 가까운 스테이징 조건과 부하 fixture를 만든다. 테스트 통과는 결론이 아니라 다음 질문을 좁힌 증거다.
부록 F 구조대 용어 카드
- 재현: 같은 조건과 행동으로 같은 실패를 다시 관찰하는 일.
- 특성 테스트: 옳고 그름을 판단하기 전 현재 동작을 기록해 변경을 감지하는 테스트.
- 회귀: 이전에 되던 동작이 변경 뒤 깨지는 현상.
- 멱등성: 같은 요청을 여러 번 처리해도 최종 효과가 한 번과 같게 만드는 성질.
- fixture: 테스트가 반복해 사용하는 고정 입력과 응답 데이터.
- 경계: 인증, 저장, 외부 전송처럼 실패 비용과 책임이 달라지는 지점.
- 카나리: 새 버전을 작은 범위에 먼저 노출해 위험을 관찰하는 배포.
- 롤백: 이전 코드만이 아니라 데이터와 외부 효과까지 안전한 상태로 되돌리는 절차.
- 관측 가능성: 로그·지표·추적으로 내부 상태를 추론할 수 있는 능력.
- 검증 계약: 목표, 제약, 완료 조건, 실행할 확인을 AI와 사람 사이에 명시한 약속.
용어를 외우는 것이 목적은 아니다. 장애 보고에서 “중복이 생겼다”를 “동일 requestId 재생이 멱등하지 않다”로 구체화하면 담당자가 볼 경계가 선명해진다. 반대로 전문 용어가 사용자 안내에 그대로 나오지 않게 한다. 사용자에게는 “같은 요청이 두 번 접수되지 않았습니다”처럼 결과와 다음 행동을 설명한다.
최신 공식 확인처
Codex의 지원 기능과 설정 방식은 바뀔 수 있다. 실제 프로젝트에 적용할 때는 위 공식 문서의 현재 설명과 이 책의 예제를 대조한다.
저작권과 예제 사용
RoomRelay, 조직, 사용자, 예약 데이터는 이 책을 위해 만든 가상 예제다. 코드와 그림은 원고의 교육 목적에 맞춰 새로 작성했다. 독자는 학습과 내부 실험에 예제를 변형할 수 있지만 실제 서비스에 적용하기 전 보안·개인정보·결제·접근성 검토를 별도로 수행해야 한다. Codex, Node.js와 기타 제품명은 각 소유자의 상표이며 이 책은 공식 인증이나 제휴를 주장하지 않는다.
공식 참고 자료
기술·가격·정책은 바뀔 수 있으므로 실제 적용 전에 아래 원문을 다시 확인하세요.
