d3ro-voice/docs/design/04-verification-report.md
Yun Chan e24bb8378c 초기 프로젝트 설정: 하네스 시스템 + 설계서 + RE 노하우
- CLAUDE.md: 프로젝트 규칙, 기술 스택, 코딩 규칙, 페이즈 로드맵
- .claude/settings.json: 권한, 강제 훅 (매 프롬프트 설계서 규칙 주입)
- .claude/skills/: implement-phase, review-phase, scaffold, test-commit, debug
- .claude/agents/: electron-architect, voice-pipeline-expert, ui-specialist
- docs/design/00-09: 마스터 아키텍처, 서비스 명세(16개), IPC(113채널),
  DB스키마, UI컴포넌트, 검증리포트, 외부엔진연동, 갭분석,
  VoiceMode패턴, 디자인시스템, 히스토리팝업
- docs/phases/1-7+3.5: 전체 구현 페이즈 문서
- docs/re-findings/: Speakly RE 노하우 5개 문서
2026-04-05 01:03:03 +09:00

22 KiB

04. 설계서 vs Speakly 소스 대조 검증 리포트

검증일: 2026-04-04 검증 대상: 설계서 00~03 vs Speakly 추출 소스 (/tmp/speakly-extracted/dist/src/) 검증자: verifier agent


요약

등급 건수 설명
Critical 5 설계서가 잘못 반영하거나 핵심 로직이 누락된 항목
Major 10 기능 동작에 영향을 줄 수 있는 불일치/누락
Minor 8 정확도 개선이 필요한 세부 사항

1. 상태 머신 검증

CRITICAL-01: RecognitionState에 CONNECTING, DESTROYED 누락

  • 설계서 위치: 02-ipc-and-types.md 394-403행, 01-service-specifications.md 559-569행
  • 실제 소스 위치: types/recording.js 34-54행
  • 차이점: Speakly RecognitionState는 9개 상태를 정의한다:
    • IDLE, PREPARING, CONNECTING, READY, RECOGNIZING, COMPLETED, CANCELLED, ERROR, DESTROYED
  • 설계서 02에는 CONNECTING 누락, COMPLETING 상태가 추가되어 있다 (Speakly에 없음).
  • 설계서 01에는 Processing 상태가 추가되어 있으나, Speakly에는 Processing이 없고 RECOGNIZING 상태에서 서버 측 처리가 진행된다.
  • DESTROYED는 dispose 전용 최종 상태로, 설계서에 누락.
  • 수정 제안:
    • CONNECTING 상태 추가 (WebSocket/sidecar 연결 중)
    • COMPLETINGRECOGNIZING의 하위 플래그로 변경하거나 제거
    • DESTROYED 상태 추가 (리소스 정리 완료 전용)
    • Processing은 로컬에서는 LLM 후처리 시 의미가 있으나, Speakly의 원래 상태와 다름을 명시

MAJOR-01: AudioState는 일치

  • 결과: 설계서 AudioState (IDLE, INITIALIZING, STREAMING, STOPPED) = Speakly 소스 완전 일치. 문제 없음.

MAJOR-02: RecordingTipUIState 누락

  • 실제 소스 위치: types/recording.js 60-76행
  • 차이점: Speakly에는 RecordingTipUIState 열거형이 별도 존재:
    • OPENING, RECORDING, THINKING, CANCELLED, NOTICE, ERROR, ERROR_WITH_RETRY
  • 설계서에 RecordingTip UI 상태 매핑이 없다.
  • 수정 제안: 02-ipc-and-types.md 또는 03-db-and-ui.md에 RecordingTipUIState 추가

2. 이벤트 페이로드 검증

MAJOR-03: VoiceMode 열거형 불일치

  • 설계서 위치: 01-service-specifications.md 579-585행, 02-ipc-and-types.md 388행
  • 실제 소스 위치: VoiceModeService.js 89-95행
  • 차이점: Speakly VoiceMode는 4개 모드:
    • DICTATION, HANDS_FREE, CUSTOM_INSTRUCTION, HANDS_FREE_NO_WAKE
  • 설계서는 dictationhands-free 2개만 정의.
  • 수정 제안:
    • custom-instruction 모드는 D3RO의 CustomInstructionService로 매핑 가능, 설계서에 명시 필요
    • hands-free-no-wake 모드는 검증 항목 3에서 별도 기술

MAJOR-04: HotkeyService 이벤트 구조 차이

  • 설계서 위치: 00-master-architecture.md 257-262행
  • 실제 소스 위치: HotkeyService.js 35-51행
  • 차이점:
    • 설계서: hotkey:dictation-pressed, hotkey:dictation-released, hotkey:dictation-double-press, hotkey:command-pressed
    • Speakly 실제: 범용 hotkey:pressed / hotkey:released 이벤트에 commandId 페이로드 포함
    • 더블 프레스는 HotkeyService가 아니라 VoiceModeService에서 타이밍으로 감지
  • 수정 제안: HotkeyService 이벤트를 범용 hotkey:pressed(commandId, timestamp) / hotkey:released(commandId, timestamp) 패턴으로 변경. 더블 프레스 감지는 VoiceModeService 책임으로 명시.

MINOR-01: 오디오 샘플레이트 불일치

  • 설계서 위치: 01-service-specifications.md 68행 (sampleRate: 16_000)
  • 실제 소스 위치: config/constants.js 47행 (SAMPLE_RATE: 24000)
  • 차이점: 설계서는 Whisper 기본인 16kHz를 기술하나, Speakly는 24kHz로 캡처한다 (Opus 인코딩 후 서버 전송).
  • 수정 제안: D3RO는 로컬 Whisper를 사용하므로 16kHz가 맞을 수 있으나, Speakly 원본이 24kHz임을 각주로 명시. 코덱이 Opus인 경우 24kHz가 필요할 수 있음.

3. 누락 패턴 검증

CRITICAL-02: accidentalPress 감지 패턴 미기술

  • 실제 소스 위치: VoiceModeService.js 116행, 3251-3293행
  • 패턴 설명:
    • MIN_AUDIO_DURATION_MS = 700ms 미만 키 누름은 accidental press로 판정
    • 두 가지 판정 기준: (1) key press duration < 700ms, (2) session lifetime < 700ms
    • 세션에 accidentalPress = true 마킹 후 즉시 취소
    • checkAndMarkAccidentalPress() 메서드로 녹음 정지 전에 조기 체크
  • 설계서 상태: 완전 누락
  • 수정 제안: 01-service-specifications.md VoiceModeService 섹션에 accidentalPress 감지 로직 추가:
    키 릴리스 시점에서 duration < MIN_AUDIO_DURATION_MS(700ms) → 세션 취소
    

CRITICAL-03: Audio Mute 연동 패턴 미기술

  • 실제 소스 위치: VoiceModeService.js 118-120행, 2726-2810행
  • 패턴 설명:
    • 녹음 시작 시 시스템 오디오 음소거 (NativeService.muteSystemAudio())
    • MUTE_DELAY_MS = 500ms — 사운드 이펙트 재생 후 음소거
    • wasMutedBeforeRecording — 이미 음소거였으면 녹음 후 언뮤트 안함
    • UNMUTE_SOUND_DELAY_MS = 100ms — 언뮤트 후 종료 효과음 재생 지연
    • config:getMuteAudioWhenDictating 설정으로 on/off 가능
    • 루프백 마이크 사용 시 음소거 스킵 (_isLoopbackMic())
  • 설계서 상태: 완전 누락
  • 수정 제안: 01-service-specifications.md VoiceModeService에 mute/unmute 시퀀스 추가

CRITICAL-04: Retry 로직 (이전 세션 오디오 재전송) 미기술

  • 실제 소스 위치: VoiceModeService.js 181-182행, 2563-2594행, 3316-3320행+
  • 패턴 설명:
    • retryRecognition(historyId) — 이전 세션의 저장된 오디오 파일을 읽어 재전송
    • retryingSession / retryingHistoryId 상태 추적
    • history:retry IPC 채널로 렌더러에서 호출 가능
    • ESC 키로 retry 취소 가능
    • No-wake 모드 취소 시 undoNoWakeCancel() → 자동 retry
    • 오디오 파일이 디스크에 저장된 세션만 retry 가능
  • 설계서 상태: 완전 누락
  • 수정 제안: 별도 섹션으로 Retry 흐름도 추가

CRITICAL-05: Action Queue (이벤트 직렬화) 패턴 미기술

  • 실제 소스 위치: VoiceModeService.js 186-275행
  • 패턴 설명:
    • actionQueue: NXAction[] — 키보드 이벤트를 큐에 넣고 순차 처리
    • isProcessingActionQueue 플래그로 동시 실행 방지
    • 핫키 press/release 이벤트 간 race condition 방지
    • NXAction 클래스: type, timestamp, hotkeyId, hotkeyTimestamp
    • ESC 시 clearActionQueue()로 전체 큐 클리어
  • 설계서 상태: 완전 누락
  • 수정 제안: VoiceModeService 명세에 Action Queue 패턴 추가. 이것은 동시 키 이벤트 안정성의 핵심.

MAJOR-05: hands-free-no-wake 모드 미기술

  • 실제 소스 위치: VoiceModeService.js 94행, 103행, 656-689행
  • 패턴 설명:
    • 별도 핫키로 진입하는 핸즈프리 모드
    • 녹음 중 취소 시 오디오를 디스크에 보존 (cancelledSessionId)
    • 나중에 undoNoWakeCancel()로 자동 retry 가능
    • getNoWakeModeEnabled() 설정으로 on/off
  • 설계서 상태: 완전 누락
  • 수정 제안: VoiceModeService VoiceMode 열거형에 추가, 전용 핫키 등록 로직 기술

MAJOR-06: RecordingTip 2-phase 리사이즈는 기술됨, 하지만 preload 전략 누락

  • 설계서 위치: 02-ipc-and-types.md 159-166행 (tipMeasured, tipPrepare, tipShow)
  • 실제 소스 위치: RecordingTipWindow.js 157행 (preload() 메서드)
  • 차이점: 설계서는 2-phase IPC는 기술했으나, RecordingTipWindow의 preload() 메서드 — 앱 시작 시 윈도우를 미리 생성해놓고 숨겨두는 전략이 누락.
  • 수정 제안: 초기화 시퀀스(00-master 단계 10)에 "팝업 윈도우 프리로드" 설명 보강

MAJOR-07: 마이크 디바이스 팁(NewMicrophonePrompt) 미기술

  • 실제 소스 위치: main/index.js 74행, 1090-1108행, main/NewMicrophonePromptWindow.js
  • 패턴 설명:
    • 새 마이크 디바이스 연결 시 전용 프롬프트 윈도우 표시
    • "이 마이크로 전환?" + "다시 묻지 않기" 옵션
    • config:getDontPromptNewMicrophone / config:setShowMicDeviceTip 설정
    • NewMicrophonePromptWindow 클래스 — 독립 윈도우
  • 설계서 상태: 완전 누락
  • 수정 제안: 03-db-and-ui.md에 NewMicrophonePrompt 윈도우 추가

MAJOR-08: ResultPopup auto-close 타이밍 미기술

  • 실제 소스 위치: main/ResultPopupWindow.js 27-28행, 249-269행
  • 패턴 설명:
    • 기본 autoCloseDelay = 10000ms (10초)
    • 마우스 호버 시 타이머 일시 정지
    • 마우스 떠나면 타이머 재개
    • autoCloseDelay: 0이면 auto-close 비활성화
    • content-ready 이벤트 시 타이머 시작
  • 설계서 상태: 02-ipc-and-types.mdwindow:showResultPopup 채널은 있으나 auto-close 동작 미기술
  • 수정 제안: ResultPopup 동작 명세에 auto-close 타이밍 추가

MAJOR-09: Typing Nudge 패턴 미기술

  • 실제 소스 위치: VoiceModeService.js 196-208행, 706-796행
  • 패턴 설명:
    • 사용자가 5초간 연속 타이핑 (10키 이상) 시 "음성 사용해보세요" 넛지 표시
    • 하루 최대 3회, 30분 간격
    • 오늘 이미 음성 사용했으면 억제
    • 자정 넘으면 자동 리셋
    • typing-nudge 이벤트 emit
  • 설계서 상태: 완전 누락
  • 수정 제안: D3RO에서 이 기능이 필요한지 결정 후, 채택 시 VoiceModeService 명세에 추가

MAJOR-10: Mode Switch 패턴 (녹음 중 모드 전환) 미기술

  • 실제 소스 위치: VoiceModeService.js 626-651행
  • 패턴 설명:
    • 녹음 중 다른 모드 핫키 누르면 오디오 청크를 보존하며 모드 전환
    • savedAudioChunks = [...this.audioChunks] → cancelRecording → startRecording(savedAudioChunks)
    • sessionType: 'mode-switch' — 시작 사운드 억제, UI 전환 최소화
  • 설계서 상태: 완전 누락
  • 수정 제안: VoiceModeService 시퀀스 다이어그램에 mode-switch 분기 추가

4. IPC 채널 누락 분석

설계서 IPC 채널 수: 113개 (02-ipc-and-types.md 기준) Speakly 실제 IPC 채널 수: ~160개 (preload/index.js에서 추출)

의도적 제거 (클라우드/인증 관련) — 문제 없음

네임스페이스 채널 수 이유
auth:* 11 로컬 전용, 인증 불필요
feedback:* 1 클라우드 피드백
userInfo:* 6 클라우드 사용자 정보
update:* 8 Phase 7으로 연기
logUpload:* 4 클라우드 로그 업로드
banner:* 4 클라우드 배너
report:* 1 클라우드 오류 보고
diagnostics:* 3 디버그 진단
debug:* 1 접근성 트리 탐색

실수 누락 가능성 (검토 필요)

채널명 설계서 Speakly 판정
history:retry 없음 있음 누락 — retry 기능에 필수
history:getLatestId 없음 있음 누락 — undo/retry에 사용
history:getRecentSessions 없음 있음 누락 — 대시보드에 사용
history:deleteByDuration 없음 있음 선택적 — 짧은 녹음 일괄 삭제
history:getStats / resetStats 없음 있음 stats:*로 통합된 것으로 보이나 확인 필요
config:getMuteAudioWhenDictating 없음 있음 누락 — mute 연동 설정
config:getSelectedMicrophone 없음 있음 누락 — 마이크 선택 저장
config:setSelectedMicrophone 없음 있음 누락
config:getTranslateLanguage 없음 있음 누락 — 번역 언어 설정
config:getSoundEffectsEnabled 없음 있음 누락system:isSoundEnabled으로 매핑?
config:getDontPromptNewMicrophone 없음 있음 누락 — 마이크 프롬프트 설정
config:shouldShowHandsFreePromo 없음 있음 선택적 — 프로모션 UI
hotkey:getHandsFreeNoWakeShortcut 없음 있음 누락 — no-wake 모드 핫키
hotkey:setHandsFreeNoWakeShortcut 없음 있음 누락
hotkey:getVoiceModeEnabled 없음 있음 누락 — 모드별 활성화 토글
hotkey:setVoiceModeEnabled 없음 있음 누락
hotkey:pauseVoiceMode 없음 있음 누락 — 설정 UI 중 핫키 일시정지
hotkey:resumeVoiceMode 없음 있음 누락
hotkey:validateHotkey 없음 있음 누락 — 핫키 유효성 검증
hotkey:resetToDefault 없음 있음 누락 — 기본값 복원
hotkey:getAvailableKeys 없음 있음 누락 — 사용 가능한 키 목록
hotkey:setOverrideAppBundleId 없음 있음 선택적 — 온보딩/테스트 전용
hotkey:refreshRequired 없음 있음 누락 — 핫키 재등록 알림
clipboard:copy 없음 있음 누락 — 결과 클립보드 복사
input:insertText 없음 있음 system:insertText로 매핑됨, 확인 필요
input:getCursorState 없음 있음 선택적 — postInsertCursorContext
app:getActiveApplication 없음 있음 system:getActiveApp으로 매핑됨
app:getAppIcon 없음 있음 선택적 — 앱 아이콘 표시
voice:startWithCustomInstruction 없음 있음 누락 — Custom Instruction 녹음
voice:stopForCustomInstruction 없음 있음 누락
voice:cancel 없음 있음 누락 — 별도 취소 채널 (voice:cancelRecording과 다름)
customInstruction:* (9개+) 있음(일부) 있음(많음) 키 이벤트 관련 채널 다수 누락

5. 타이밍 상수 검증

상수 설계서 값 Speakly 실제 값 일치 비고
오디오 샘플레이트 16,000 Hz 24,000 Hz 불일치 설계서는 Whisper 기본, Speakly는 Opus 기반
오디오 프레임 크기 60ms 60ms 일치
바이트/프레임 1,920 계산 시 2,880 (2400020.06) 불일치 샘플레이트 차이에 따름
디바이스 폴링 간격 2초 확인 필요 -
ERROR_AUTO_HIDE_DELAY 미기술 3,000ms 누락
ERROR_WITH_RETRY_AUTO_HIDE_DELAY 미기술 10,000ms 누락
NOTICE_AUTO_HIDE_DELAY 미기술 3,000ms 누락
MAX_RETRY_ATTEMPTS 미기술 3 누락
PRESS_HOLD_THRESHOLD 미기술 10ms 누락 Speakly 주석에 "changed from 300ms"
SECOND_PRESS_THRESHOLD 미기술 300ms 누락 더블 프레스 감지
STOP_DELAY_MS 미기술 200ms 누락 키 릴리스 후 정지 지연
MIN_AUDIO_DURATION_MS 미기술 700ms 누락 accidentalPress 판정
MUTE_DELAY_MS 미기술 500ms 누락 사운드 이펙트 후 음소거
UNMUTE_SOUND_DELAY_MS 미기술 100ms 누락 언뮤트 후 효과음 지연
POST_RECORDING_WAIT_TIME 미기술 4,000ms 누락 녹음 후 연결 대기
RETRY_MAX_CONNECT_WAIT_TIME 미기술 15,000ms 누락 파일 리플레이 연결 대기
FINISH_IDLE_TIMEOUT 미기술 30,000ms 누락 commit 후 완료 대기
FINISH_MAX_WAIT 미기술 120,000ms 누락 완료 절대 최대 대기
ResultPopup autoCloseDelay 미기술 10,000ms 누락
HEARTBEAT_INTERVAL 미기술 5,000ms 누락 WebSocket 핑 간격
HEARTBEAT_TIMEOUT 미기술 15,000ms 누락 서버 응답 타임아웃
SESSION_MONITOR_INTERVAL 미기술 5,000ms 누락 세션 모니터링 타이머
SYNC_DELAY_MS (HotkeyService) 미기술 500ms 누락 핫키 동기화 디바운스

수정 제안: 01-service-specifications.md 각 서비스 섹션에 타이밍 상수 표 추가. D3RO 고유 값이 다를 경우 주석으로 Speakly 원본 값 명시.


6. DB 스키마 검증

MINOR-02: history 테이블 컬럼 차이

컬럼 설계서 Speakly 실제 판정
focused_app_bundle_id 제거됨 있음 설계서에서 의도적 제거 (macOS 전용) → OK
window_web_title 제거됨 있음 설계서에서 의도적 제거 → OK
window_web_domain 제거됨 있음 설계서에서 의도적 제거 → OK
window_web_url 제거됨 있음 설계서에서 의도적 제거 → OK
audio_metadata 제거됨 있음 (JSON) 설계서에서 의도적 제거 → OK
mic_device_info 제거됨 있음 (JSON) 설계서에서 의도적 제거 → OK
user_id 제거됨 있음 로컬 단일 사용자 → OK
error_code 추가됨 없음 D3RO 신규 — OK
stt_model 추가됨 없음 D3RO 신규 — OK
llm_model 추가됨 없음 D3RO 신규 — OK
stt_latency_ms 추가됨 없음 D3RO 신규 — OK
llm_latency_ms 추가됨 없음 D3RO 신규 — OK
selected_text 미언급 없음 설계서 "제거" 목록에 있으나 Speakly에도 없음 → 각주 정정 필요

MINOR-03: history 모드 열거형 차이

  • 설계서: 'dictation' | 'translate' | 'command'
  • Speakly: 'dictation' | 'hands-free' | 'translation'
  • 수정 제안: D3RO 고유 모드명 사용은 문제 없으나, Speakly 원본과의 매핑을 각주로 명시

MINOR-04: stats 테이블 차이

  • 설계서에 streak_days, last_session_at 추가 — Speakly에 없는 D3RO 신규 필드 → OK
  • Speakly의 stats 테이블은 설계서와 동일한 구조 (id=1 싱글턴)

MINOR-05: 인덱스 차이

  • 설계서: idx_history_created_at (단일 컬럼)
  • Speakly: idx_history_user_created_at (user_id, created_at 복합 인덱스)
  • 로컬 단일 사용자이므로 설계서의 단순화가 적절 → OK

7. 추가 발견 사항

MINOR-06: NativeService koffi+DLL 대체 방안 명확화 필요

  • 실제 소스: NativeService.js 44행 — koffi 모듈로 네이티브 DLL 로드
  • 설계서 위치: 00-master-architecture.md 166행 — "제거, npm 패키지로 대체"
  • 이슈: NativeService는 키보드 모니터링, 마이크 캡처, 시스템 오디오 음소거, 앱 모니터링 등 광범위한 기능 제공. 이를 대체할 npm 패키지 목록이 불완전.
  • 수정 제안: NativeService가 제공하는 기능별 대체 패키지 매핑 표 작성:
    • 키보드 모니터링 → uiohook-napi (기술됨)
    • 마이크 캡처 → 미명시 (Web Audio API? node-audiorecorder?)
    • 시스템 오디오 음소거 → 미명시 (loudness? @aspect-build/napi-audio?)
    • 앱 모니터링 → 미명시 (active-win?)
    • 클립보드 → @nut-tree/nut (기술됨)
    • 텍스트 삽입 → @nut-tree/nut (기술됨)
    • 핫키 인터셉트 → uiohook-napi (기술됨)

MINOR-07: TextOperationStrategy의 앱별 삽입 전략 미기술

  • 실제 소스: TextOperationStrategy.js 12-36행
  • 패턴: 앱별로 다른 텍스트 삽입 방법 (clipboard vs keyboard), 선택 방법 (clipboard vs ax vs none), 검증 모드 (auto vs skip) 설정
  • 설계서: TextInsertService에서 일률적으로 "clipboard save → set → Ctrl+V → restore" 기술
  • 수정 제안: 앱별 규칙(rules) 시스템 최소한으로 기술. D3RO 로컬 환경에서는 원격 config 불필요하나, 기본 fallback 규칙은 유지 필요.

MINOR-08: 세션 모니터링 패턴

  • 실제 소스: VoiceModeService.js 184-185행, 209-210행
  • 패턴: sessionMonitorTimer (5초 간격) — 좀비 세션 감지 및 정리
  • 설계서 상태: 누락
  • 수정 제안: VoiceModeService 안정성 패턴으로 추가

8. 설계서에 추가해야 할 패턴 목록 (우선순위순)

# 패턴 중요도 대상 설계서
1 Action Queue (이벤트 직렬화) Critical 01-service-specifications.md
2 accidentalPress 감지 Critical 01-service-specifications.md
3 Audio Mute 연동 Critical 01-service-specifications.md
4 Retry 로직 (오디오 파일 재전송) Critical 01-service-specifications.md
5 RecognitionState CONNECTING/DESTROYED Critical 02-ipc-and-types.md
6 hands-free-no-wake 모드 Major 01-service-specifications.md
7 Mode Switch (녹음 중 모드 전환) Major 01-service-specifications.md
8 ResultPopup auto-close 타이밍 Major 03-db-and-ui.md
9 NewMicrophonePrompt 윈도우 Major 03-db-and-ui.md
10 RecordingTipUIState 열거형 Major 02-ipc-and-types.md
11 타이밍 상수 표 Major 01-service-specifications.md
12 누락 IPC 채널 (~25개) Major 02-ipc-and-types.md
13 NativeService 대체 패키지 매핑 Minor 00-master-architecture.md
14 앱별 TextOperation 규칙 Minor 01-service-specifications.md
15 Typing Nudge Minor 결정 후 추가
16 세션 모니터링 타이머 Minor 01-service-specifications.md
17 RecordingTip preload 전략 Minor 00-master-architecture.md

9. 결론

설계서는 Speakly의 기본 아키텍처와 주요 서비스 구조를 잘 반영하고 있으나, VoiceModeService의 핵심 런타임 패턴(Action Queue, accidentalPress, mute 연동, retry, mode switch)이 거의 완전히 누락되어 있다. 이 패턴들은 사용자 경험의 안정성과 직결되므로 구현 전에 반드시 설계서에 반영해야 한다.

또한 IPC 채널이 약 25개 누락되어 있으며, 특히 hotkey 관련 채널(pause/resume, validate, availableKeys, no-wake 단축키)과 history:retry 채널이 중요하다.

상태 머신은 대체로 일치하나 CONNECTINGDESTROYED 상태가 빠져 있어 상태 전이 다이어그램 수정이 필요하다.