마지막 업데이트 2026-07-22
각 발견 항목의 우선순위 점수 = 카테고리 가중치 × 심각도(1~5). 가중치는 목표 배분에 따름.
| 카테고리 | 가중치 | 점수 범위 | 의미 |
|---|---|---|---|
| tech-debt | 0.5 | 0.5 ~ 2.5 | 기술부채 해결 — 구조·컨벤션·안전망(CI/테스트/타입) 문제 |
| potential-bug | 0.3 | 0.3 ~ 1.5 | 잠재 버그 — 특정 조건에서 장애·데이터 불일치·누수 유발 |
| legacy | 0.2 | 0.2 ~ 1.0 | 레거시/죽은 코드 제거 — 도달 불가·미사용 코드 |
| 점수 | 분류 | 위치 | 내용 |
|---|---|---|---|
| 2.5 | tech-debt | .github/workflows/* | CI에 타입체크·린트·테스트 게이트 전무. 5개 워크플로우 모두 수동 배포 전용(workflow_dispatch), PR/push 트리거 0건 → 타입오류가 프로덕션까지 통과 가능 |
| 2.0 | tech-debt | entities/guest-session/model/use-ai-session.ts | 3,119줄 god-hook. 60+ refs, 25 useEffect가 RTC·TTS·STT·turn-detection·interrupt·transcript를 한 훅에 응집. V2 핵심 경로 최대 리스크 |
| 2.0 | tech-debt | entities/guest-page-session/model/use-guest-page-session.ts | 2,447줄, 32 useEffect. V2 게스트 페이지 세션 전체가 단일 훅 — 최우선 분해 대상 |
| 2.0 | tech-debt | turbo.json:13 + 각 package.json | check-types 태스크가 완전 no-op(어느 앱에도 스크립트 없음). strict:true가 어디서도 강제 안 됨 |
| 2.0 | tech-debt | apps/web/**/*.test.ts (13개) | 고아 테스트 — vitest 의존성·test 스크립트·config 전무로 실행 불가능한 테스트 13개 방치 |
| 2.0 | tech-debt | lib/utils.ts:209 외 3곳 | roomId 파싱 4중 중복(utils.ts 실사용 + room-id.ts 데드 + tts/authz.ts·host-intervention 로컬 복제 + shared room.ts) — 단일화 필요 |
| 2.0 | tech-debt | apps/web/lib/api/* | controller·service·dao 레이어 절반만 마이그레이션(4개 라우트만 경유, lesson/session 5개+internal은 db-queries 직접 호출) — 이중 접근 구조 |
| 2.0 | tech-debt | apps/web/types/props/ | 중앙 Props 컨벤션 붕괴 — 인라인 Props 204곳 vs 중앙 1개, import 0건. CLAUDE.md 규칙과 실제 괴리 → 규칙 폐기 or 복원 결정 필요 |
| 2.0 | tech-debt | apps/stt/internal/audio/{processor,filter,limiter}.go | 서버측 DSP 파이프라인 ~300줄 전체가 죽은 코드(실제 필터링은 브라우저 WebAudio). Python 포팅 잔재 — 3파일 통째 삭제 가능 |
| 1.5 | potential-bug | socket/src/redis/room-store.ts:154 + reaper.ts:28 | 죽은 인스턴스의 room이 reaper CAS 실패로 영구 orphan(pin TTL 90s < reap 시점 ~105s → 결정론적). 유령 room이 활성 목록에 잔존 |
| 1.5 | potential-bug | socket/src/redis/client.ts:163 + reaper-scheduler.ts:85 | Redis 순단 시 heartbeat 유실 → 살아있는 인스턴스가 reap됨(split-brain). 진행 중 수업 세션 강제 정리 가능 |
| 1.5 | tech-debt | apps/socket/tsconfig.json:10-24 | strict:false + noImplicitAny:false + strictNullChecks:false — 실시간 미디어 서버의 타입 안전성 사실상 꺼짐 |
근본 원인 두 가지: ① 라우트에 withAuthMember 류 인증 래퍼 누락, ② lib/s3.ts 헬퍼가 사용자 제공 s3Key를 prefix 검증 없이 사용. 모범 패턴은 리포 안에 이미 존재(audio-analysis-logs/download-url의 prefix 검증, mfcc-download-url의 DB 저장 key 사용) — 동일 패턴 적용이 처방.
| 심각도 | 위치 | 내용 |
|---|---|---|
| S5 | api/recording-captions/route.ts:11-53 | 무인증 POST가 captionS3Key로 임의 S3 객체 본문 응답 — ${stage}/ice/main_key.json 주입 시 TURN API 키 유출 확정 |
| S5 | api/recording-download/route.ts:7-22 | 무인증 POST가 body.s3Key 검증 없이 signed URL 발급 — 아동 녹음/키파일 전체 다운로드 가능 |
| S5 | api/session-logs/route.ts:194-235 | 무인증 DELETE가 s3Key를 그대로 삭제 헬퍼에 전달 — 임의 S3 객체 삭제(데이터 파괴) |
| S4 | api/session-logs/route.ts:128-192 | POST/PATCH 무인증(GET만 인증) — 아동 대화 로그 위변조 |
| S4 | api/lesson-progress/route.ts:6 | POST 완전 무인증 — body userId로 임의 아동 레슨 진도 조작 |
| S4 | api/lessons/[userId]/[index]/sessions/route.ts:23 | POST 무인증 세션 레코드 주입(GET도 무인증 조회) |
| S4 | api/guest-data/route.ts:6-8 | 무인증 GET — userId 열거로 아동 수업 PII IDOR |
| S4 | api/issue-texts/route.ts:58-173 | POST/PUT/DELETE/PATCH 무인증(GET만 인증) |
| S4 | api/realtime/call/route.ts:11-45 | 무인증 POST가 OpenAI Realtime 유료 세션 생성 — 비용 남용 벡터 |
| S3 | api/internal/user-profile/[userId]/route.ts:36 | 로그인 member면 소유권 검사 없이 임의 userId 프로필 반환(IDOR) |
| S3 | api/users/route.ts:32 | 전체 사용자 목록을 pin 등 PII 스트리핑 없이 반환(단일조회는 제거하는데 목록은 노출) |
| S3 | api/ice-servers/route.ts:14-38 | 무인증 GET이 metered TURN 자격증명 반환 — 무단 relay 사용 가능 |
| S3 | api/resources/upload-url/route.ts:8 | 무인증 presigned upload URL(최대 100MB) — 스토리지 남용 |
V2·prompt-test는 V1 훅을 전혀 import하지 않음(grep 0건). socket 쪽도 V2가 V1 코드를 import하는 건 0건 — 얽힘은 소수 공유 컴포넌트/유틸에 국한되어 클린 제거 가능.
| 점수 | 분류 | 위치 | 내용 |
|---|---|---|---|
| 2.0 | tech-debt | entities/guest-session/model/use-ai-session.ts | 3,119줄 god-hook — RTC/DataChannel/TTS/VAD·turn-detection/STT/interrupt/mic/response 큐/transcript 응집. 분리 후보: tts · stt · rtc-connection · turn-detection |
| 2.0 | tech-debt | entities/guest-page-session/model/use-guest-page-session.ts | 2,447줄, 32 useEffect, 17 useRef — V2 게스트 페이지 전체가 단일 훅 |
| 1.5 | tech-debt | app/monitor-dashboard/[group]/[roomId]/page.tsx | 1,973줄 페이지 컴포넌트(19 useEffect) — 위젯/훅 추출 필요 |
| 1.5 | tech-debt | entities/monitor-session/model/use-monitor-session.ts | 1,540줄 + as any 31개 밀집(V2 최다) — transcript 타입 정의 누락 |
| 1.5 | tech-debt | features/session/ui/session-card.tsx | 1,303줄 "카드" 컴포넌트(12 useEffect/11 useState) |
| 1.5 | tech-debt | entities/monitor-controls/model/use-guest-controls.ts:5-9 | FSD 역방향: entities→features 런타임 값 import — 역방향 의존 중 가장 심각 |
| 1.5 | tech-debt | features/monitor/hooks/use-session-checker.ts:3 · features/session/ui/{card-toast-overlay,transcript-history}.tsx | FSD 역방향: features→widgets 3건(ToastAlimtalkAction · ToastData · ScrollToBottomButton) |
| 1.0 | tech-debt | shared/ui/guest-layout-content.tsx:4 · entities/guest-page-session/model/use-guest-page-session.ts:4 | shared→entities 타입 역전 1건 + 형제 entity deep-path import(barrel 우회) 1건 |
| 1.0 | tech-debt | hooks/mediasoup/use-mediasoup-{consumer,producer}.ts:661,323 | (transport as any)._handler._pc — mediasoup 비공개 내부 접근, 버전업 시 RTT 수집 조용히 파손 |
| 1.0 | tech-debt | V2 전반 (use-monitor-session 67 · consumer 57 · external-stt-observer 42 …) | raw console.* 약 200개 — createLogger와 혼용, 로깅 체계 불일치(Loki 포맷 이탈) |
| 1.0 | tech-debt | stores/use-language-guard-settings-store.ts:216-294, 691-693 | 수동 deep-equal(payloadEquals) drift 증거(speechOutput 중복 검사) + 모듈 최상위 hydrateFromServer() side-effect(import만 해도 fetch) |
| 0.9 | potential-bug | hooks/mediasoup/use-mediasoup-consumer.ts:769-828 | removePeerStream이 setState updater 안에서 외부 배열 push(순수성 위반) — StrictMode 이중실행 시 중복 정리 + N회 렌더 |
| 0.9 | potential-bug | hooks/mediasoup/use-mediasoup-producer.ts:409-484,580-641 | camera/ai produce effect에 mic effect가 가진 disposed 가드 부재 — cleanup 경합 시 producer 누수 |
| 0.6 | potential-bug | hooks/mediasoup/use-mediasoup-producer.ts:90-243 | createTransport deps에 attemptReconnect 누락 — stale closure |
| 0.6 | legacy | entities/guest-session/model/{use-transcripts,use-guest-connection}.ts | 초기 FSD scaffolding 잔재 2파일 — import 0건, 삭제 가능 |
| 0.4 | legacy | widgets/monitor/ui/{issue-input-panel,monitor-settings-modal}.tsx · monitor-dashboard/…/focus-toast-layer.tsx | 미사용 위젯/컴포넌트 3파일 — 참조 0, 삭제 가능 |
보안 13건은 §2로 분리. 아래는 그 외 항목.
| 점수 | 분류 | 위치 | 내용 |
|---|---|---|---|
| 2.0 | tech-debt | lib/utils.ts:209 · lib/room-id.ts:8 · lib/tts/authz.ts:141 · features/host-intervention/api/log-intervention.ts:22 | roomId 파싱 4중 중복 — utils.ts로 단일화, room-id.ts(전체 데드)는 삭제 |
| 2.0 | tech-debt | lib/api/ (controllers·services·daos) | 레이어 마이그레이션 절반 — 4개 라우트만 경유, 나머지 5개+internal은 db-queries 직접 호출. lesson.dao vs lesson-progress.dao 중복 DAO 포함 |
| 2.0 | tech-debt | types/props/ | 중앙 Props 컨벤션 붕괴(인라인 204곳 vs 중앙 1개, import 0) — CLAUDE.md와 괴리, 규칙 결정 필요 |
| 1.5 | tech-debt | apps/web/logger.js vs packages/shared/src/utils/logger.ts | Logger 이중 구현 + web logger의 프로덕션 게이팅 주석처리(#shouldLog 항상 true) |
| 1.5 | tech-debt | package.json(web) | 미사용 deps: mysql2 · lamejs(+고아 types/lamejs.d.ts) · @huggingface/transformers(죽은 serverExternalPackages) · react-grab(CDN 로드) |
| 0.9 | potential-bug | lib/tts/authz.ts:40 vs lib/tts/limits.ts:27 | MAX_TEXT_LENGTH 2000 vs 1900 불일치 — 1901~2000자를 429(rate limit)로 오응답 |
| 0.9 | potential-bug | lib/api/services/lesson-session.service.ts:55 | sessionId = ${lessonIndex}#${Date.now()} — 동일 ms 재입장 충돌 가능 |
| 0.9 | potential-bug | api/lesson-templates/[id]/route.ts:51 · api/curriculum-categories/[id]/route.ts:28,45 | 비트랜잭션 read-modify-write lost-update + 존재확인 없는 Update/Delete(고아 참조 유발) |
| 0.9 | potential-bug | next.config.mjs:34 | 보안 헤더 전무(CSP/HSTS/X-Frame-Options/X-Content-Type-Options) — 아동 영상 WebRTC 앱에 클릭재킹/스니핑 노출 |
| 0.9 | potential-bug | scripts/import-activity-templates.js:52 | 멱등성 가드 없음 — 재실행 시 전체 템플릿 중복 삽입. copy-lesson-template.js 등 prod STAGE 하드코딩+무가드 덮어쓰기 |
| 0.8 | legacy | lib/tts/openai.ts 전체 + api/tts/stream/route.ts | OpenAI TTS 경로 프로덕션 도달 불가(클라이언트 3곳 모두 typecast 하드코딩) + 완전 데드 스트림 라우트 — 통째 삭제 후보 |
| 0.8 | legacy | packages/shared/src/{types/webrtc.ts, types/session.ts 일부, utils/constants.ts 4블록} | shared 대량 데드 — webrtc.ts 13개 타입 전부·WEBRTC_CONFIG 등 4블록(~150줄) 사용처 0 (SOCKET_EVENTS·MEDIASOUP_CONFIG는 실사용) |
| 0.6 | potential-bug | db-queries.ts:1780 · server.js:38-42 · api/holidays/route.ts:39 | getUserByName 전체 테이블 Scan / 메트릭 타이머 abort 누락 / month NaN 미검증 등 소규모 버그군 |
| 0.6 | legacy | CLAUDE.md (PWA 절) | PWA 전면 제거됐으나 문서는 상세 유지(layout.tsx는 오히려 SW 등록해제 마운트) — 스테일 문서, 인수인계 오해 유발 |
| 0.5 | tech-debt | lib/{room-id,audio-resampler,socket-debug}.ts | importer 0인 데드 파일 3개(각 16·25·38줄) + 데드 export 다수(avatar-id 3종, suggestKeyFromLabel 등) |
| 0.4 | legacy | api/class-mgmt/classes/counts · api/members/[id] GET · types/db/monitoring-group.types.ts | 호출처 0 라우트/핸들러/타입 — 삭제 가능 |
| 0.5 | tech-debt | types/error/index.ts:8 vs shared · api/resources/urls/route.ts · db-queries.ts:3163 | SessionError 이름 충돌 중복 정의 / GET·POST presign 블록 복붙 / DEPRECATED 주석 잔재 |
V1 핸들러 제거(§4)와 별개로, 운영 중인 V2 경로 자체의 안정성 이슈가 다수. 특히 Redis 2건은 결정론적/저확률-고피해 조합.
| 점수 | 분류 | 위치 | 내용 |
|---|---|---|---|
| 1.5 | potential-bug | redis/room-store.ts:154-169 + reaper.ts:28-36 | 죽은 인스턴스 room 영구 orphan — pin TTL(90s)이 최소 reap 시점(~105s)보다 항상 먼저 만료 → REAP_ROOM_LUA skip, roomKey는 TTL 없어 영구 잔존. 결정론적 발생 |
| 1.5 | potential-bug | redis/client.ts:163-168 + reaper-scheduler.ts:85-94 | heartbeat 쓰기 실패(순단+offlineQueue:false) 시 살아있는 인스턴스가 reap — 진행 중 세션 강제 정리(split-brain). two-phase가 같은 키만 재검사해 방어 불가 |
| 1.5 | tech-debt | apps/socket/tsconfig.json:10-24 | strict 계열 전부 off + allowUnreachableCode:true — 단계적 strict 도입 필요 |
| 1.2 | potential-bug | sfu-socket/handlers/room-handlers.ts:68-265 | GUEST_REQUEST_ENTRY 전체 try/catch 부재 — Redis 실패 시 callback 미도달 → 아동 입장 화면 무한 대기 |
| 1.2 | potential-bug | recording/recordingManager.ts:909-937 | createPlainTransportConsumer — connect/consume throw 시 PlainTransport close 없이 누수(반복 실패 시 누적) |
| 1.2 | potential-bug | recording/recordingManager.ts:1493-1554, 1405-1447 | transcode/concat ffmpeg 타임아웃 부재 — hang 시 세마포어(max 2) 영구 점유 → 이후 모든 녹음 transcode 블록 |
| 0.9 | potential-bug | mediasoup/routerManager.ts:137-164 · transportManager.ts:46-72, 83-86 | 동시 createRouter 경쟁 orphan(인플라이트 캐시 없음) / setMaxIncomingBitrate 실패 orphan / transport routerclose 미구독(좀비) |
| 1.5 | tech-debt | mediasoup/routerManager.ts:192-214, 578-593 | closeRouter/closeAll이 transport 정리 미배선 — 이미 구현된 closeTransportsByRoom 등이 호출 0(dead). 배선하면 위 좀비 문제 동시 해결 |
| 0.9 | potential-bug | recording/recordingManager.ts:1384-1403, 1484-1489, 389-393 · udpPort.ts:7-27 | killFfmpeg exit 무한 대기 / stdin EPIPE 리스너 부재(uncaught) / replaceAiConsumer aiPair 덮어쓰기 누수 / TOCTOU 포트 예약 |
| 0.9 | potential-bug | redis/peer-store.ts:58-174 · room-store.ts:35-53 · reaper-scheduler.ts:110-146 | peer upsert/remove 비원자(역인덱스 stale) / setRoomMeta가 HINCRBY 카운터 덮어씀 / two-phase 후 leader 재확인·재진입 가드 없음 |
| 0.9 | potential-bug | sfu-socket/handlers/monitoring-handlers.ts:138-228 · connection-handlers.ts:34-135 · room-handlers.ts:371-398 · session-handlers.ts 다수 | HOST_START_MONITORING hang / JOIN_ROOM 중복정리가 redirect 판정보다 먼저(타 회기 파괴) / FORCE_KICK 1초 후 socketId 재확인 없음 / payload 권한·가드 비일관 |
| 1.0 | tech-debt | recording/recordingManager.ts (1,740줄) | 거대 클래스 — 녹음 상태·ffmpeg 수명·세그먼트·캡션 좌표 혼재. gracefulDrain/kill/stopFfmpeg 3중 중복, transitionFfmpeg ~185줄 |
| 1.0 | tech-debt | connection-handlers.ts:46-75 ↔ room-handlers.ts:93-118 ↔ signalingHandler.ts:83-91 | "중복 게스트 정리" 3중 복붙 + monitoring-handlers↔signalingHandler 순환 의존 — cleanupGuestPeer() 추출 |
| 1.0 | tech-debt | socket 테스트 전반 | 테스트 8개가 순수 함수 위주 — mediasoup 생명주기·V2 핸들러·reaper/leader election·recordingManager 본체 전부 무커버 |
| 0.6 | potential-bug | media-handlers.ts:99-164 · transport-handlers.ts:32,62 · room-handlers.ts:451 · settings-store.ts:38-70 | callback 가드 비일관 / SET_AUTO_RESPONSE await 누락 / setter만 try/catch 없음 등 S2 버그군 |
| 1.0 | tech-debt | sfu-config.ts:54-65 · memory-only dead path · mediasoup 미사용 함수 7개 · describeError 중복 외 | S1~2 데드/중복 잔재군 — config.server(3002) 미사용, getRedis()===null 도달 불가 분기(CLAUDE.md 불일치), N+1 redis.exists 등 |
| 점수 | 분류 | 위치 | 내용 |
|---|---|---|---|
| 2.0 | tech-debt | internal/audio/{processor,filter,limiter}.go (~300줄) | 서버측 DSP 파이프라인 통째 데드 — 실제 HPF+Limiter는 브라우저 WebAudio에서 수행. Python 포팅 잔재, 3파일 삭제 가능 |
| 1.2 | potential-bug | internal/handler/websocket.go:273-436 | conn.WriteJSON을 배치 goroutine들과 read-loop이 뮤텍스 없이 동시 호출 — gorilla/websocket 단일 writer 규약 위반 → data race, 최악 패닉 |
| 1.5 | tech-debt | internal/diarization/speaker.go:40-161 (~110줄) | Jaro-Winkler 텍스트 유사도 화자매칭 전체 미사용(실사용은 First-Speaker Rule만) — 삭제 가능 |
| 0.9 | potential-bug | internal/handler/websocket.go:287-316,420 + session.go | AIContext/TurnIndex를 goroutine에서 락 없이 읽고 씀(session mu 우회) — data race |
| 0.9 | potential-bug | internal/handler/websocket.go:362-416 | handleProcessAIAudio가 STT API를 동기(≤10s)로 호출해 read-loop 블록 — 오디오 프레임 백프레셔/유실 위험 |
| 0.9 | potential-bug | entities/guest-session/lib/external-stt-observer.ts:262-288 | WS 재연결 시 서버가 새 세션 생성 → TurnHistory/dedup 상태 전부 소실 + S3 sessionId 변경(세션 연속성 유실) |
| 0.6 | legacy | external-stt-observer.ts:175-202 | "monitor"/"finalized" 메시지 핸들링 — 서버는 절대 송신 안 함(batch_result/ai_stt뿐). 구 스트리밍 모드 데드 브랜치 |
| 1.0 | tech-debt | internal/{session,stt,audio}/* 잔여 | 미사용 심볼군 — CommitCurrentTurn(Deprecated)·GetCombinedAudio·PCMToWAV류·Streaming config 잔재·client.Close() 미호출 / batch.go dedup 로직 인라인 중복 / chirp_3 하드코딩 이원화 |
| 0.4 | legacy | messages.go:26-27 · use-stt-fallback.ts:57-60 · external-stt-observer.ts:151 | Suppressed 항상 false(미구현 잔재) / serviceURI 데드 설정(Web Speech 구식 경로) / "ppipythonstt" URL 네이밍 잔재 |
| 0.5 | tech-debt | websocket.go:26-28 · external-stt-observer.ts:312 | CheckOrigin 항상 true / deprecated ScriptProcessorNode(→AudioWorklet 이전 권장) |
| 점수 | 분류 | 위치 | 내용 |
|---|---|---|---|
| 2.5 | tech-debt | .github/workflows/* (5개) | CI 품질 게이트 전무 — 전부 workflow_dispatch 배포 전용, PR/push 트리거·lint·test·tsc 실행 0건 |
| 2.0 | tech-debt | turbo.json:13 | check-types 태스크 no-op(어느 앱에도 스크립트 없음) |
| 2.0 | tech-debt | apps/web (테스트 러너 부재) | 고아 테스트 13개 — vitest 의존성·스크립트·config 전무로 실행 불가 |
| 1.5 | tech-debt | apps/socket, apps/stt (lint 부재) | ESLint가 web에만 존재 — socket/stt는 turbo lint에서 조용히 스킵 |
| 0.9 | potential-bug | apps/web/package.json:10 | next lint는 Next.js 16에서 제거된 명령 — 실행 시 깨질 가능성(로컬 검증 필요) |
| 1.0 | tech-debt | root package.json:26 · packages/shared/package.json | Tailwind v3(root)+v4(web) 혼재, root autoprefixer 미사용 / shared의 서버 SDK mediasoup 미사용 선언 + 버전 제각각 |
| 1.0 | tech-debt | CLAUDE.md | 경로 드리프트 — /main/preset·/main/template·socket/server.js·lib/openai.ts 실재하지 않음. apps/stt 앱 자체가 문서에 없음 |
| 0.4 | legacy | root package.json:18-20 · Dockerfile.dev · terraform/branch-deploy/userdata.sh | 존재하지 않는 앱 참조 dev 스크립트(client-web/server-sfu) + DEPRECATED 스텁 2파일 잔존 |
| 0.5 | tech-debt | apps/socket/vitest.config.ts:12 · eslint.config.mjs:48 · next.config.mjs:10 | 커버리지 범위 redis만 / react-hooks/exhaustive-deps off(위 훅 deps 버그들과 직결) / reactStrictMode:false |