혼자 여러 역할로 QA 1차 — 브랜치 미동기화와 잔존 토큰의 함정
📚 NestJS + Refine 풀스택 트러블슈팅 시리즈 (46편)
혼자서 BE·FE·QA·PM을 동시에 맡는 환경에서 학습 클라이언트 웹 프로토타입 첫 QA 라운드를 돌렸다. 브랜치 미동기화로 인한 미구현 오보, 만료 토큰 잔존으로 인한 401, UI 스모크 수준의 검증 충실도 부족까지 세 함정을 한 라운드에 만났고, 다음 라운드부터의 운영 체크리스트로 정리했다.
💡 Tip. 바쁜 현대인들을 위한 본문 요약
- 환경: 혼자서 BE·FE·QA·PM 네 역할을 동시에 맡는 1인 개발 구조에서 학습 클라이언트 웹 프로토타입의 첫 QA E2E 라운드를 돌렸다
- 함정 1 (오보): QA 워크트리에서
git merge dev를 누락해 FE 코드가 없는 상태로 테스트 → “페이지 미구현” 보고가 FE에 전달되는 사고- 함정 2 (401): 로그인 후
/home이동 시 401 —localStorage에 남아 있던 만료 토큰이 새 토큰을 덮기 전에 첫 요청에 실려 나간 패턴- 함정 3 (충실도): 첫 라운드가 “UI 스모크” 수준에 머물러 비즈니스 로직(번들 엣지 케이스·타이머)이 통과처럼 보였다 → 명세를
qa-edge-case-spec.md로 분리- 메타 교훈: 혼자서 여러 역할을 맡는 환경에서는 역할 간 인터페이스를 명세로 고정해 둬야 한다 — 인수인계 메모가 아니라 체크리스트 형태로
🎯 동기 — 왜 1인이 QA 역할을 따로 분리했나
이 프로젝트는 혼자서 굴린다. 보통은 BE 코드를 짜다가 그 위치에서 Postman으로 두드려 보고, FE에서 한 번 더 만져 보고 끝낸다. 그게 빠르고, 그게 솔직한 1인 개발자의 일상이다.
문제는 학습 클라이언트 웹 프로토타입이 들어왔을 때 생겼다. 화면이 14개로 늘어났고, 인증·메인·일일 숙제·신기록·설정의 다섯 흐름이 서로 얽혔다. 개발자 자신이 짠 코드는 잘 안 깨진다. 정확히는, 깨질 만한 시나리오를 안 밟는다. 매번 같은 계정으로 같은 흐름만 타고, 발견된 버그는 그 위치에서 고치고 다음으로 넘어가니까 회귀 테스트가 비어 있다.
그래서 한 라운드를 떼어 QA 역할을 따로 두기로 했다. 같은 사람이지만 도구·환경·체크리스트를 분리한다. 코드를 읽지 않는 사람의 시선으로 시나리오만 따라가는 라운드.
📌 핵심: “혼자서 QA를 한다”는 말의 진짜 의미는 체크리스트와 환경을 분리해서 코드 작성자의 가정을 끊는 것이다. 같은 머신·같은 IDE에서 같은 흐름을 두 번 돌리는 건 QA가 아니라 두 번째 수동 테스트다.
분리의 단위는 셋으로 잡았다.
- 워크트리: BE·FE·QA를 각각
git worktree로 분리. 코드를 보는 사람과 시나리오를 따라가는 사람이 다른 디렉토리에 있는 상태를 강제한다. - 체크리스트: 시나리오를 사전에 24건 적어 두고, 그 외의 우회 흐름은 라운드 중에 안 만진다.
- 보고 양식: PASS / FIXED / BLOCKED 세 라벨 + 짧은 코멘트. 자유 서술 금지.
이 셋이 첫 라운드에서 어떻게 무너졌는지가 이 글의 본론이다.
🛠️ 설계 — 워크트리·체크리스트·E2E 라벨 구조
라운드를 돌리기 전에 셋업한 구조를 먼저 그려 둔다. 함정을 읽으려면 무엇이 깔려 있었는지부터 봐야 한다.
워크트리 3개
# 본 레포 (개발 메인)
~/work/learning-platform # branch: dev
# BE 전용
git worktree add ../learning-be feature/qa-target-be
# FE 전용
git worktree add ../learning-fe feature/qa-target-fe
# QA 전용 (시나리오 따라가기)
git worktree add ../learning-qa feature/qa-work
세 디렉토리가 같은 .git을 공유한다. BE에서 커밋한 내용이 FE 디렉토리에 자동으로 보이지 않는 게 핵심이다. FE 워크트리는 feature/qa-target-fe 브랜치를 보고 있고, BE 작업이 끝나서 dev에 머지됐다 해도 FE 브랜치에서 git merge dev를 하지 않으면 그 변경이 안 들어온다. QA 워크트리도 마찬가지다.
git worktree는 같은 레포의 다른 브랜치를 다른 디렉토리에 동시 체크아웃해 주는 명령이다. 공식 문서는 “단일 작업 디렉토리의 제약을 푼다”고 정리하지만, 1인 개발에서는 그 이상이다. 역할 간 인터페이스를 디렉토리 분리로 강제하는 도구가 된다.
시나리오 체크리스트 24건
스프레드시트 한 장으로 정리했다.
| 흐름 | 시나리오 수 | 대표 케이스 |
|---|---|---|
| 인증 | 3 | 로그인 정상/오류, 로그아웃 |
| 메인 화면 | 2 | 회원 정보, 오늘 활동, 교육과정 진행률 |
| 일일 숙제 | 11 | 숙제 시작, 번들 진입, 콘텐츠 제출, 복습 모듈, 번들 완료 |
| 신기록 도전 | 3 | 목록, 도전, 결과 |
| 설정 | 2 | 화면, 비밀번호 변경 |
| 배치고사 진입 | 3 | 시작 페이지, 코드 입력, 결과 화면 |
각 시나리오는 짧은 입력·기대 응답·통과 조건만 적혔다. “왜 이 시나리오인지”는 안 적었다. 안 적은 게 함정 3의 씨앗이었다는 건 라운드가 끝난 뒤에야 알았다.
결과 보고 3 라벨
라운드를 한 번 끝낸 뒤 보고는 짧게 끝내야 1인이 굴린다.
- PASS — 시나리오 그대로 통과
- FIXED — QA가 직접 잡은 버그까지 포함 (한 줄 코멘트 + 커밋 해시)
- BLOCKED — 페이지 자체가 동작 안 함 (의존 작업 미완료로 분류)
PR 코멘트나 이슈 트래커는 안 만들었다. 어차피 같은 사람이라 컨텍스트 스위칭이 곧 비용이다. 라벨과 한 줄 코멘트로 끝.
📝 실전 사례 — 한 라운드에 만난 세 함정
함정 1: QA 워크트리에서 git merge dev를 누락한 사고
라운드를 시작하고 첫 두 시간은 매끄러웠다.
인증 정상 — PASSED
로그아웃 — PASSED
메인 화면 — FIXED (home API 연동 3건 수정, cf5d743)
신기록 목록 — PASSED
일일 숙제 — BLOCKED (페이지 미구현)
설정 — BLOCKED (페이지 미구현)
여기서 손이 멈췄다. 일일 숙제와 설정이 미구현이라니. FE가 한 시간 전에 “숙제·신기록·설정 화면 완료” 보고를 올렸는데 페이지가 없다.
PM 역할로 전환해서 FE에 검증 실패 통지를 보냈다. 03:00 KST. 한 시간이 지나기 전이라 FE 컨텍스트가 아직 따끈했고, 본인이 짠 코드라 곧장 반박이 돌아왔다.
FE: pages/assignment/index.tsx — 존재합니다.
pages/settings/index.tsx — 존재합니다.
App.tsx 라우트도 정상입니다.
라우팅을 다시 검증한 결과는 분명했다.
<Route path="/assignment/:assignmentId" element={<AssignmentPage />} />
<Route path="/assignment/:assignmentId/bundle/:bundleId" element={<BundlePage />} />
<Route path="/assignment/:assignmentId/complete" element={<AssignmentCompletePage />} />
<Route path="/settings" element={<SettingsPage />} />
코드는 있다. 그런데 QA 워크트리에서는 없다. 답은 단순했다.
# QA 워크트리에서 확인
cd ../learning-qa
git log --oneline -5
# (FE의 0f1b3e4 커밋이 안 보인다)
git status
# On branch feature/qa-work
# Your branch is up to date with 'origin/feature/qa-work'
git log feature/qa-work..dev --oneline
# 0f1b3e4 feat(learning-prototype): 숙제·신기록·설정 흐름
# eca6d02 feat(learning-prototype): 인증·메인 흐름
QA 브랜치가 FE 브랜치보다 2 커밋 뒤에 머물러 있었다. git merge dev를 안 한 상태로 라운드를 시작한 것이다. FE는 코드를 짰고, dev에 머지까지 했고, 라우트도 정상이다. 단지 QA가 본 디렉토리에 그 코드가 없었을 뿐이다.
3:30에 git merge dev를 한 줄 친 뒤 다시 돌렸더니 24건 전부 통과했다.
⚠️ 주의: 혼자서 여러 역할을 맡는 환경에서 가장 위험한 사고가 이 사고다. 다른 사람에게 잘못 책임을 추궁하는 것과 달리, 1인 환경에서는 자기 자신을 의심하지 않는다. “내가 짠 코드인데 왜 없겠어”가 아니라 “내가 짠 코드인데 왜 다른 워크트리에 있겠어”가 진실이다.
이 사고가 남긴 자동화는 한 줄이다.
# QA 라운드 시작 전 첫 명령 — 강제
cd ~/work/learning-qa && git fetch origin && git merge dev
이걸 pre-qa.sh 스크립트로 묶고, QA 라운드 시작 시마다 첫 줄에 두었다. 라운드 2부터는 같은 사고가 한 번도 안 났다.

함정 2: 로그인 후 401 — 만료된 토큰의 잔존
같은 날 오후 13:00. 라운드를 한 번 더 돌렸다. 인증·메인까지 통과한 뒤, 로그아웃 → 다시 로그인 → 메인 화면 이동 흐름에서 한 번도 보지 못했던 에러가 터졌다.
GET /home 401 Unauthorized
{ "statusCode": 401, "message": "Unauthorized" }
콘솔에서 네트워크 탭을 열어 봤다.
GET /home
Authorization: Bearer eyJhbGciOiJIUzI1NiI...{만료된 토큰}
방금 로그인했는데 만료된 토큰이 실려 나간다. 첫 가설은 “로그인 응답이 만료 토큰을 줬다”였지만 그건 아니었다. POST /auth/login 응답에는 fresh 토큰이 정상으로 들어 있었다.
localStorage를 까보니 답이 보였다.
// Application 탭 → localStorage
accessToken: "eyJhbGciOiJIUzI1NiI...{만료된 옛 토큰}"
로그인 응답에는 새 토큰이 왔지만, localStorage.setItem('accessToken', ...) 호출이 한 박자 늦었다. 라우팅 처리(navigate('/home'))가 토큰 저장보다 먼저 일어났고, 새 화면이 첫 API를 호출할 때 localStorage에서 읽은 토큰은 아직 옛 것이었다.
Before — 토큰 저장이 navigate 뒤에
// pages/login/index.tsx — ❌ 순서가 뒤집힌 패턴
const onSubmit = async (data: LoginForm) => {
const res = await axios.post('/auth/login', data);
navigate('/home'); // 먼저 이동
localStorage.setItem('accessToken', res.data.accessToken); // 나중에 저장
};
navigate가 비동기로 새 페이지를 마운트하고, 그 페이지의 useEffect가 /home API를 호출한다. 그 시점에 새 토큰은 아직 저장 안 됐다.
After — 저장 → 이동 + 기존 토큰 명시 삭제 + axios interceptor
// pages/login/index.tsx — ✅ 저장 먼저, 이동은 그 다음
const onSubmit = async (data: LoginForm) => {
// 1) 기존 토큰 명시 삭제 (이전 세션 잔존 방어)
localStorage.removeItem('accessToken');
const res = await axios.post('/auth/login', data);
// 2) 새 토큰 저장 먼저
localStorage.setItem('accessToken', res.data.accessToken);
// 3) 그 다음 이동
navigate('/home');
};
// App.tsx — axios response interceptor 추가
axios.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
localStorage.removeItem('accessToken');
window.location.href = '/login';
}
return Promise.reject(error);
}
);
두 줄짜리 수정이지만 두 번을 분리한 이유가 있다.
- 로그인 흐름 자체의 순서 보정 — 같은 사고를 못 만들게 막는다
- interceptor의 401 처리 — 다른 경로(토큰 자연 만료, 로그아웃 누락)에서 들어와도 자동 복구된다
후자가 없으면, 사용자가 30분 동안 화면을 떠났다가 돌아왔을 때 같은 401이 다시 나고 그 화면이 빈 상태로 멎는다. 두 줄을 같이 깔아야 다음 라운드에서 같은 함정이 안 터진다.
🔍 단서:
localStorage와navigate가 섞인 흐름에서 401이 나면, 먼저 저장 순서를 의심해야 한다. JWT 자체의 만료보다 흔한 원인은 “새 토큰이 도착했는데 옛 토큰이 첫 요청에 실려 나간 한 박자 지연”이다.
JWT 토큰 운영의 표준 패턴은 공식 문서가 잘 정리해 둔다.
함정 3: “UI 스모크”에 멈춘 검증 충실도 — 비즈니스 로직을 안 밟았다
이틀 뒤(01-19) 같은 라운드를 한 번 더 돌렸다. 24건 전부 PASS. 첫 QA E2E를 통과 처리했다.
그날 저녁 PM 역할로 라운드 결과를 다시 읽다가 손이 멈췄다. 통과한 24건이 전부 UI가 뜨는지·버튼이 눌리는지 수준이었다. 번들이 정확히 어떤 콘텐츠 후보로 채워지는지, 타이머가 음수로 들어갔을 때 어떻게 표시되는지, 신기록이 정확히 어느 시점에 갱신되는지는 아무도 안 봤다.
QA 라운드를 돈 사람은 “시나리오를 끝까지 따라가면 PASS”라는 룰을 정확히 지켰다. 다만 그 시나리오에는 엣지 케이스가 한 줄도 없었다.
회고는 짧았다.
- 첫 라운드 24건은 “UI 스모크”로 명시 — 통과해도 비즈니스 로직 검증은 아님
- 별도 명세
docs/pm/qa-edge-case-spec.md를 만들어 엣지 케이스 5건(EC-1~5)을 분리 - 라운드 2부터는 UI 스모크 → 엣지 케이스 두 단계로 분리해서 돌림
엣지 케이스 5건은 이렇게 잡았다.
| 코드 | 흐름 | 검증 포인트 |
|---|---|---|
| EC-1 | 번들 진입 후 중간 종료 → 재진입 | 진행 상태 복원 |
| EC-2 | 같은 콘텐츠 후보가 두 번 등장 | 중복 차단 또는 의도된 반복? |
| EC-3 | 타이머 0초에서 자동 제출 | NaN:NaN·음수 표시 방어 |
| EC-4 | 번들 완료 직후 즉시 재시작 | 동시 호출 방어 |
| EC-5 | 네트워크 단절 → 복귀 | retry 또는 fail-fast |
EC-3가 다음 라운드에서 진짜 타이머 NaN:NaN 버그를 잡았다(다음 편). 첫 라운드가 통과 처리됐다고 진짜 통과한 게 아니라는 점이 이 함정의 본질이다.
📌 핵심: 혼자서 여러 역할을 맡는 환경에서 QA가 PASS를 찍었다는 보고는 체크리스트가 충분히 충실한지부터 의심해야 한다. PASS의 의미는 “체크리스트 안에서 통과”이지 “코드가 옳음”이 아니다. 같은 사람이 QA 체크리스트도 짰다면 그 체크리스트의 충실도가 곧 QA의 충실도다.
💡 교훈 — 1인 다중 역할 QA의 안티패턴과 권장 패턴
세 함정은 셋 다 역할 간 인터페이스가 메모리에 있었던 사고다. 다른 사람과 일했다면 자연스럽게 명세로 들어갔을 것들이 같은 사람의 머릿속에 있다 보니 묵계로 흘러갔다.
세 함정을 한 표로 묶었다.
| 함정 | 안티패턴 | 권장 패턴 |
|---|---|---|
| 워크트리 동기화 | QA가 “어차피 같은 레포니까” 머지를 생략 | 라운드 시작 첫 줄로 git fetch && git merge dev 강제 스크립트 |
| 토큰 잔존 | 로그인 흐름이 navigate → setItem 순 | removeItem → setItem → navigate + 401 interceptor 두 겹 |
| 검증 충실도 | UI 스모크 체크리스트만 보고 PASS 처리 | UI 스모크와 엣지 케이스 명세를 두 파일로 분리, 두 단계 라운드 |
여기에 혼자서 여러 역할을 맡는 환경에서 한 번 더 강조해야 할 메타 교훈이 있다.
자기 자신을 가장 의심하기 어렵다. 다른 사람이 짠 코드면 “혹시 푸시 안 했나” 같은 가설이 자연스럽게 떠오르지만, 자기가 짠 코드는 “분명히 푸시했지”라는 확신이 먼저 떠오른다. 그 확신이 함정 1에서 한 시간을 잡아먹었다.
방어책은 확신을 명령으로 환원하는 것이다. 머릿속의 “분명히 했지”를 명령 한 줄(git log feature/qa-work..dev --oneline)로 바꿔 두면, 그 한 줄이 빈 출력으로 떨어지는 순간 확신이 깨진다. 명세·체크리스트·자동화 스크립트는 죄다 이 환원의 도구다.
안 통한 방어책
이번 라운드를 돌리면서 시도했다가 안 통한 것도 둘 있다.
- “QA 라운드 직전 git pull로 충분하다” —
pull은 현재 브랜치를 원격과 맞출 뿐이다. QA 브랜치(feature/qa-work)가 FE 브랜치(feature/qa-target-fe→dev)의 새 커밋을 자동으로 가져오지 않는다.merge한 줄이 필수다. - “같은 머신이니 토큰은 한 번만 신경 쓰면 된다” —
localStorage는 세션과 무관하게 살아남는다. 디버깅용으로 만료 토큰을 한 번 넣어 두면, 이후 정상 로그인 흐름도 함께 깨진다. 잔존 토큰 명시 삭제는 옵션이 아니라 기본값이다.
🚀 권장 — 1인 다중 역할 QA 체크리스트
다음 라운드부터 한 장으로 정리한 체크리스트다. 길지 않아야 매번 돌릴 수 있다.
라운드 전
- QA 워크트리에서
git fetch origin && git merge dev실행 -
git log {qa-branch}..dev --oneline— 출력이 비어 있는지 확인 - 빌드/시드/마이그레이션 모두 최신 dev 기준 재실행
- 브라우저
localStorage·sessionStorage·쿠키 전부 삭제 (수동 또는clear())
라운드 중
- 시나리오는 사전 체크리스트 24건만 — 즉흥 흐름 금지
- PASS / FIXED / BLOCKED 세 라벨로만 보고, 자유 서술은 한 줄까지
- 버그 발견 시 그 위치에서 고치지 말고 라운드 끝까지 기록만 (수정은 별도 역할로 전환 후)
라운드 후
- 통과한 시나리오가 “UI 스모크” 수준인지 “비즈니스 로직 검증” 수준인지 표기
- BLOCKED 항목은 의존 작업과 매핑 — 단순 미구현인지, 브랜치 누락인지 분리
- 다음 라운드용 엣지 케이스 명세 1~5건 추가
함정 자동 차단
# scripts/pre-qa.sh — QA 라운드 시작 강제 진입점
set -euo pipefail
cd "$(git worktree list --porcelain | awk '/worktree.*learning-qa/ {print $2}')"
git fetch origin
git merge dev --no-edit
PENDING=$(git log "$(git rev-parse --abbrev-ref HEAD)..dev" --oneline)
if [ -n "$PENDING" ]; then
echo "[FAIL] QA branch behind dev:"
echo "$PENDING"
exit 1
fi
echo "[OK] QA worktree synced to dev"
이 스크립트가 첫 줄에 들어간 뒤로 함정 1은 한 번도 재현 안 됐다.
axios interceptor 패턴은 React 진영의 표준 가이드를 그대로 옮겼다.
localStorage가 페이지 전환을 가로지른다는 점은 MDN의 한 문장이 정확하다.
“데이터는 페이지 세션을 가로질러 유지되며, 사이트 전체에서 액세스할 수 있다.”
이 한 문장이 혼자서 여러 역할을 맡는 환경에서 토큰을 다룰 때 함정의 본질이다. 세션과 무관하게 살아남는다는 사실을 잊으면, 어제 넣은 만료 토큰이 오늘의 401로 돌아온다.
📋 정리 — 핵심 요약
| 함정 | 신호 | 진짜 원인 | 적용한 방어 |
|---|---|---|---|
| 브랜치 미동기화 | ”페이지 미구현” 오보가 FE에 전달 | QA 워크트리가 FE 브랜치보다 2 커밋 뒤 | pre-qa.sh로 git merge dev 강제 |
| 토큰 잔존 | 로그인 직후 /home 401 | setItem 전에 navigate가 먼저 → 옛 토큰이 첫 요청에 실림 | removeItem → setItem → navigate + 401 interceptor |
| 검증 충실도 부족 | 24건 전부 PASS인데 비즈니스 로직 미검증 | 체크리스트가 “UI 스모크”만 다룸 | qa-edge-case-spec.md 분리, 라운드 2단계화 |
혼자서 여러 역할을 맡는 QA의 메타 정리
| 측면 | 안티패턴 | 권장 |
|---|---|---|
| 역할 분리 | 머릿속의 묵계 | 워크트리·체크리스트·스크립트로 명시 |
| 자기 확신 | ”내가 했지” | 명령 한 줄로 검증, 빈 출력이면 다시 의심 |
| 보고 양식 | 자유 서술 | 3 라벨 + 한 줄 코멘트 |
| 충실도 검증 | PASS = 코드 옳음 | PASS = 체크리스트 안에서 통과 |
| 토큰 운영 | 한 번 저장하면 끝 | 잔존 토큰 명시 삭제 + interceptor 두 겹 |

숫자로 보는 첫 라운드
- 시나리오: 24건 (UI 스모크 수준)
- 함정: 3건 (브랜치 / 토큰 / 검증 충실도)
- 첫 함정에서 잡아먹힌 시간: 약 1시간 (02:30 ~ 03:30)
- 재발 방지에 필요한 코드:
pre-qa.sh한 줄, axios interceptor 8줄, 명세 파일 1개 - 이후 같은 라운드에서 재발한 함정: 0건 (라운드 2~7)
이 한 라운드의 가치는 24건 통과가 아니라 3개 함정을 명세에 명시한 흔적이다. 혼자서 여러 역할을 맡는 환경에서 QA가 의미를 가지려면, 통과한 시나리오 수가 아니라 명시한 함정 수로 라운드를 평가해야 한다.

다음 편에서는 라운드 2에서 EC-3가 잡은 타이머 NaN:NaN 버그를 다룬다. 같은 타이머 컴포넌트가 같은 코드로 두 번 마운트되는데, 한 번은 정상이고 한 번은 NaN:NaN으로 뜨던 패턴이다.
📚 NestJS + Refine 풀스택 트러블슈팅 시리즈 (46편)
- 1. 왜 NestJS + Prisma를 선택했나 — B2B SaaS 백엔드 기술 선택기
- 2. 도메인 모델링 첫날 — B2B SaaS의 핵심 엔티티 정의하기
- 3. 27개 테이블의 탄생 — Prisma 스키마 설계기
- 4. 권한 매트릭스 — Admin/운영자/사용자 3역할 설계
- 5. BigInt PK에서 Int PK로 — 첫 번째 스키마 리팩토링
- 6. Seed 데이터의 함정 — FK 삭제 순서 삽질기
- 7. DDD를 도입하기로 했다 — Repository/Domain/Application 3계층
- 8. 인터페이스 구현체로 바꾸는 날 — NestJS DI와 TypeScript의 간극
- 9. 단위 테스트 인프라 구축 — Jest 설정부터 Mock까지
- 10. E2E 테스트와 Cloud SQL의 고난 — 4/8 passing에서 8/8까지
- 11. REST API 첫 구현 — 6개 Controller, 21개 엔드포인트 완성
- 12. v1.0 완성, 그리고 갈아엎기로 결심한 날
- 13. 번들 구조를 통째로 바꿔야 했던 이유
- 14. Phase 1 문서 정비 — Use Case를 번들 기반으로 다시 쓰다
- 15. Phase 2 스키마 마이그레이션 — 데이터 안 날리고 구조 바꾸기
- 16. Phase 3-1·3-2 — Repository와 Domain 서비스로 36개 빌드 에러 잡기
- 17. Phase 3-3·3-4·3-5 — Application부터 Module까지, v2.0 마이그레이션 닫는 날
- 18. 코드를 박은 다음 날 — 4,658줄 DDD 문서를 24분 사이에 다시 쓴 하루
- 19. v2.1 Domain Layer — 도메인 서비스 1,682줄을 한 커밋에 박은 날의 설계 철학
- 20. v3.0 Application Layer 재작성 — 도메인 서비스 위에 얇은 막을 한 Phase에 박은 날
- 21. 갈아엎고 80일 — v2.0 마이그레이션 8편 메타 회고
- 22. 1인 다역으로 5일 만에 90% — Admin Portal MVP를 끌어올린 토글 한 줄
- 23. Mock에선 되던 게 REST에선 안 됐다 — 응답 포맷 한 칸 차이가 만든 하루
- 24. CORS는 됐다 — PATCH만 빼고. allowedHeaders 한 줄과 Vite 프록시의 소문자 메서드
- 25. 멀티테넌트 누수 — tenantId 3계층 강제
- 26. Prisma 정책 싱글톤 — zod superRefine 임계값 가드
- 27. 멀티테넌트 쓰기 가드 — body.tenantId 차단과 집계 일관성
- 28. 두 번째 점검은 합류 지점이었다 — Admin Portal 2차에서 한 사이클에 잡힌 FE-BE 연동 버그 11건
- 29. Prisma 그래프 스키마 — 선형 레벨을 DAG로 옮긴 4가지 결정
- 30. 교육과정 구조 리팩토링 — 3필드 분리와 폴백 결정기
- 31. 배치고사 MVP — 자동 레벨 배치를 걷어내고 5지표 측정만 남기다
- 32. JWT Guard 적용 — request.user undefined부터 jwt malformed까지
- 33. 디버깅용 운영 API 7개 — Unity 만료 테스트 30분 대기를 0초로
- 34. NestJS Swagger 일괄 적용 — 35개 컨트롤러 + DTO 22개
- 35. Unity ↔ 웹 PostMessage 브릿지 설계기
- 36. Vuplex 브릿지 초기화 타이밍 — 첫 메시지가 증발한 이유
- 37. 콘텐츠 브릿지 10종 통합 완료 — 같은 규격으로 묶기
- 38. 지표 누계 시스템 — TOP5 순위를 INSERT 전용 스냅샷으로 굳히기
- 39. 킥오프 배치 첫 구현 — 매시 전체 EXPIRED 사고와 Winston 도입
- 40. 혼자 여러 역할로 QA 1차 — 브랜치 미동기화와 잔존 토큰의 함정
- 41. 타이머가 NaN:NaN으로 떴다 — Bundle API 응답 누락 필드와 비어 있는 콘텐츠 후보
- 42. 1인 개발 QA 5라운드 — 타이머·시드·스키마로 옮긴 버그들
- 43. Unity Lobby + 배치고사 씬 통합 — 두 클라이언트가 같은 회원을 보는 첫 빌드
- 44. 배치고사 MVP 후속 — 명세를 코드로 옮기고 레거시 571줄을 일괄 삭제하다
- 45. Problem 종속 끊기 — 1,891개 마이그레이션과 단위 테스트 38건
- 46. NestJS 권한 가드 — 목록은 막고 상세는 뚫린 날