← Docs hub

Speaker Enrollment Service Phase 1 Plan

이 문서는 화자 인식 PoC를 제품 서비스 1단계로 전환할 때, 런처앱의 음성 등록 화면과 온디바이스 AI 모듈을 어떻게 나누어야 하는지 정리한다.

핵심 결론은 런처앱은 등록 UX와 녹음 세션을 담당하고, 실제 화자 등록 판단과 embedding 저장은 온디바이스 AI 모듈이 담당하는 하이브리드 구조다.

Speaker Enrollment Phase 1 Overview

0. 현재 코드 발견과 단계 조정

2026-08-06 MR6 SKMLauncher2와 ondevice 현재 코드를 다시 대조한 결과, ondevice ForegroundService에는 이미 overlay_speaker_manage, overlay_speaker_register, STT 녹음 정지/복구, WAV 저장, embedding 등록 경로가 구현되어 있다. 반면 PUI/Launcher에서 이 내부 화면을 여는 보호된 entrypoint는 없다.

따라서 이 문서의 Launcher-native UX는 장기 제품형 목표로 유지하되, 첫 통합은 다음처럼 단계화한다.

Phase 1A
  PUI / setLauncherScreen
  -> signature permission으로 보호된 ondevice entrypoint
  -> 기존 ondevice overlay 재사용

Phase 1B
  등록 controller/API와 UI 상태 분리

Phase 1C
  Launcher-native 제품 UX
  -> bound service/AIDL로 ondevice 등록 엔진 사용

현재 코드 근거, VitalSign이 별도 앱으로 열리는 실제 경로, speakerRegistration screen name, Receiver/Activity 선택과 완료 상태 계약은 PUI Launcher 화자 등록 화면 연동 분석을 최신 기준으로 본다.

1. 1단계 목표

1단계는 “실험용 WAV 파일을 특정 폴더에 넣어두면 등록되는 PoC”가 아니라, 서비스 환경에서 사용자가 런처앱을 통해 직접 화자를 등록/삭제/조회할 수 있는 제품 경로를 만드는 단계다.

목표:

비목표:

2. 권장안: 런처앱 UX + AI 모듈 등록 엔진

Launcher And Agent Boundary

영역 런처앱 책임 온디바이스 AI 모듈 책임
화면 등록 안내, 동의, 녹음 진행률, 재녹음 안내, 등록 결과 표시 화면 직접 소유하지 않음
녹음 마이크 권한과 녹음 세션 시작/중지 UX 입력 파일/FD/PCM 품질 검사
품질 판단 사용자에게 재시도 사유 표시 무음, 짧은 발화, clipping, 다화자/겹침, 음량/거리 기준 판단
등록 등록 요청 호출 embedding 추출, speaker_id 생성, 등록/저장
저장 임시 raw audio를 넘긴 뒤 삭제 요청 또는 전달 완료 embedding/profile metadata 영속화, accepted source sample 보관/삭제
조회/삭제 등록 화자 목록 UI, 삭제 버튼 등록 목록 제공, embedding/profile/history 삭제
확장 사용자 이름/앱 계정 연결 UI 후보 speaker_context, Person Profile link 후보 제공

이 구조가 좋은 이유:

3. 대안 비교

설명 장점 문제 판단
A. 런처앱이 WAV만 저장 런처앱이 정해진 로컬 폴더에 파일만 저장하고 AI 모듈이 나중에 스캔 구현 시작은 빠름 실패가 늦게 발견됨, raw audio 잔류, 저장소/권한 결합 강함 비추천, debug/fallback 전용
B. 런처앱이 녹음 후 AI 모듈 API 호출 런처앱이 녹음 파일/FD/PCM을 넘기고 AI 모듈이 등록 처리 UX와 엔진 경계 명확, 실패 즉시 반환, 보안 관리 쉬움 API 계약 필요 1단계 추천
C. AI 모듈이 등록 UI까지 제공 우리 앱 안에 등록 화면을 직접 넣음 모듈 단독 테스트 쉬움 서비스 런처 UX와 분리, 제품 화면 일관성 낮음 PoC/개발자 화면용
D. Cloud 등록 raw voice 또는 embedding을 Cloud로 전송해 등록 기기 간 동기화 가능성 개인정보/네트워크/법무 리스크 큼 1단계 제외

4. 컴포넌트 구조

Enrollment Component Design

4.1 Launcher Enrollment UI

런처앱은 사용자에게 보이는 등록 경험을 담당한다.

주요 기능:

런처앱이 직접 결정하지 말아야 하는 것:

4.2 Speaker Enrollment Service

온디바이스 AI 모듈은 런처앱에 명시적 등록 API를 제공한다.

권장 형태:

이유:

4.3 Speaker Registration Engine

현재 PoC 근거:

SpeakerIdentificationEngine.kt
- registerSpeakerFromWavPaths()
- registerSpeakerFromWavPath()
- identifyFromSamples()
- searchEmbeddingWithScoreRaw()

DiarizationEngine.kt
- diarize(samples)
- DiarizationSegment(startSec, endSec, speakerId)

서비스 1단계에서 추가로 필요한 것:

5. 기획/개발 추가 고려사항

Planning And Development Scope

화자 등록 1단계는 “녹음 버튼 하나 추가”가 아니라 런처앱 UX, 앱 간 API, AI 모듈 저장소, 개인정보 정책, QA evidence가 동시에 맞아야 한다. 아래 항목은 기획과 개발에서 빠뜨리면 후속 단계에서 재작업이 커지는 부분이다.

5.1 런처앱 UI 컴포넌트

런처앱은 서비스 사용자가 직접 보는 화면을 담당하므로 최소한 아래 컴포넌트가 필요하다.

UI 컴포넌트 필요 이유 1단계 처리
음성 등록 진입점 사용자가 기능을 찾을 수 있어야 함 설정/프로필/개인화 영역 중 하나에 배치
기능 설명 화면 음성정보가 왜 필요한지 설명 “목소리를 구분해 개인화 서비스에 사용” 수준으로 명시
동의 화면 source sample 로컬 보관과 삭제 가능성 안내 consent version을 API에 전달
녹음 준비 화면 주변 소음/한 명 발화/가까운 거리 안내 샘플 품질 실패율 감소
녹음 진행 UI 3~5회 샘플 수집 상태 표시 sample index, accepted count 표시
품질 실패 안내 재녹음 사유를 사용자가 이해해야 함 qualityCode -> messageCode 매핑
등록 완료 화면 등록 성공과 표시 이름 확인 display name 수정 가능
등록 화자 목록 등록된 voice profile 관리 speaker list, updatedAt, 상태 표시
재등록 버튼 모델 변경/품질 개선/사용자 요청 대응 기존 speaker_id 유지 또는 신규 생성 정책 필요
삭제 버튼 개인정보 관리와 서비스 신뢰성 source sample, embedding, metadata 삭제 호출

UI 상태는 아래처럼 최소 상태 머신으로 잡는 것이 좋다.

idle
-> consent_required
-> recording(sampleIndex)
-> sample_submitting
-> retry_required(qualityCode)
-> ready_to_commit
-> committed
-> failed(errorCode)
-> canceled

기획에서 확정해야 하는 문구:

5.2 앱 간 연동 방식

런처앱과 AI 모듈은 같은 제품 안의 앱이지만, raw audio와 speaker identity를 다루므로 단순 broadcast만으로는 부족하다.

권장 구현:

항목 권장 이유
등록 API Bound Service 또는 AIDL 요청/응답, 진행률, 취소, 권한 검증 필요
파일 전달 content:// URI 또는 ParcelFileDescriptor 앱 private storage 직접 공유보다 안전
권한 signature permission 외부 앱의 등록 API 호출 차단
호출자 검증 caller package/signature 확인 런처앱만 허용
진행률 callback 또는 polling 녹음/품질검사/commit 상태 표시
취소 cancelEnrollment(sessionId) 사용자가 화면을 닫거나 실패했을 때 세션 정리
음성명령 씬 잠금 VoiceCommandSceneLock 등록 녹음 중 WUW/F2 기반 일반 음성명령 진입 차단

앱 간 통신 방식 판단:

방식 적합한 영역 한계 판단
Broadcast “상태가 바뀌었다” 같은 단방향 알림 요청/응답, 진행률, 취소, 파일 전달, 권한 검증에 약함 단독 사용 비추천
Bound Service 같은 기기 내 앱 간 상태ful API 타입 안정성과 버전 계약을 별도로 관리해야 함 1단계 단순 구현 후보
AIDL 명시적 인터페이스, 타입 계약, callback, 버전 관리 초기 작성량이 Bound Service보다 큼 제품화 기준 1순위
ContentProvider 오디오 파일/FD 전달 workflow 제어에는 부적합 파일 전달 보조 수단
Shared directory watch 구현은 빠름 race condition, 권한, cleanup, 보안 문제가 큼 debug/fallback 외 비추천

결론은 제어는 AIDL 또는 Bound Service, 오디오 전달은 ContentProvider/ParcelFileDescriptor, 상태 알림은 callback/polling, broadcast는 보조 알림으로 분리하는 것이다. 화자 등록은 단순 이벤트가 아니라 세션 기반 workflow이므로 broadcast 중심 설계로 가면 나중에 lock, 취소, timeout, 재등록, 삭제, QA evidence가 모두 흔들린다.

비추천:

5.3 등록 중 WUW/음성명령 씬 차단 계약

Voice Command Scene Lock

화자 등록 녹음은 일반 음성명령과 다른 경로다. 일반 명령은 WUW/F2 이후 acceptRecording() -> STT -> LLM -> TTS 흐름으로 들어가지만, 화자 등록은 런처앱 화면에서 사용자가 녹음 버튼을 눌러 샘플을 제출하는 흐름이다.

따라서 등록 중에는 아래 계약이 필요하다.

상태 처리
등록 준비/녹음/샘플 제출 중 일반 WUW/F2 이벤트가 들어와도 음성명령 씬으로 진입하지 않는다.
등록 중 F2/WUW 발생 start ring, acceptRecording(), STT 시작, LLM 호출, TTS 응답을 수행하지 않는다.
등록 중 F3/VAD 이벤트 발생 등록 세션의 녹음 품질 판단에만 사용하거나 무시한다. 일반 명령 종료 신호로 처리하지 않는다.
등록 완료 commitEnrollment() 이후 lock을 해제하고 일반 음성명령을 다시 허용한다.
등록 취소/실패 세션 temp 파일을 정리하고 lock을 해제한다.
앱/기기 재시작 stale lock과 timeout session을 cleanup worker가 정리한다.

권장 계약:

data class VoiceCommandSceneLock(
    val ownerSessionId: String,
    val ownerPackage: String,
    val reason: String = "SPEAKER_ENROLLMENT",
    val startedAt: Long,
    val expiresAt: Long
)

interface SpeakerEnrollmentApi {
    fun acquireVoiceCommandSceneLock(lock: VoiceCommandSceneLock): Boolean
    fun releaseVoiceCommandSceneLock(ownerSessionId: String): Boolean
    fun getVoiceCommandSceneLockState(): VoiceCommandSceneLock?
}

실제 구현에서는 startEnrollment() 성공 시 AI 모듈이 내부적으로 lock을 잡고, commitEnrollment(), cancelEnrollment(), timeout, process restart recovery에서 반드시 release하는 방식이 단순하다. 별도 API는 런처앱과 상태를 명시적으로 맞추기 위한 계약으로 둔다.

AI 모듈 내부에서 필요한 처리:

기획/UX에서 필요한 결정:

5.4 AI 모듈 개발 항목

AI 모듈 쪽 개발 항목은 아래처럼 나누는 것이 적절하다.

개발 항목 설명 우선순위
SpeakerEnrollmentApi start/submit/commit/cancel/list/delete 계약 Must
VoiceCommandSceneLock 등록 중 WUW/F2 기반 일반 음성명령 진입 차단 Must
SpeakerSampleImporter URI/FD/PCM 입력을 내부 temp 파일로 normalize Must
SpeakerSampleQualityGate silent/noise/clipping/multi/overlap 판정 Must
SpeakerEmbeddingStore embedding, metadata, source sample 저장/로드/삭제 Must
SourceSampleArchive accepted sample 암호화 보관, manifest 관리 Must
SpeakerRebuildWorker 재부팅/모델 변경 후 embedding rebuild Should
SpeakerContextBuilder 식별 결과를 speaker_context summary로 변환 Should
SpeakerAuditLogger 민감정보 없는 등록/삭제/실패 이벤트 기록 Should
debug import tool 개발용 WAV import와 품질검사 Could

5.5 저장소와 재등록 정책

재부팅 이후 사용자가 다시 녹음할 수 없는 상황을 고려하면 accepted source sample을 보관해야 한다. 단, 이 보관은 “일반 raw audio 방치”가 아니라 AI 모듈이 소유하는 관리 자산이어야 한다.

필수 정책:

재등록 정책 결정 포인트:

정책 선택지 권장
speaker_id 유지 재등록 후 기존 ID 유지 vs 새 ID 발급 같은 사용자 재등록이면 유지
source sample 교체 기존 샘플 유지 vs 새 샘플로 교체 새 샘플 품질이 통과하면 교체 또는 병합
rebuild 트리거 앱 시작 시 즉시 vs idle worker idle worker, 긴급 시 lazy rebuild
삭제 실패 부분 성공 허용 vs pending deletion pending deletion 상태로 재시도

5.6 기획 범위 분류

범위 항목
Must 등록 시작, 동의, 3개 이상 샘플 수집, 품질 실패 재시도, commit, 목록, 삭제, 로컬 source sample 보관, Cloud 미전송, 등록 중 음성명령 씬 잠금
Should 재등록 버튼, source sample 기반 rebuild, 모델 버전 변경 대응, duplicate likely 안내, 등록 상태 복구
Could 사용자별 별칭 추천, 바이탈 Face ID 연결 후보 표시, 앱 계정 연결 후보, Google 계정 개인화 연결
Out of scope Cloud voiceprint 등록, 기기 간 source sample 동기화, 음성만으로 민감 권한 부여, 감정 기반 제품 기능

5.7 QA와 릴리스 체크리스트

검증 항목 확인 방법
성공 등록 3~5개 샘플 제출 후 speaker_id와 embedding 생성
품질 실패 silent/noise/multi/overlap 샘플에서 재녹음 UI 표시
재부팅 복구 재부팅 후 등록 화자 목록과 source sample manifest 로드
rebuild embedding 삭제 또는 모델 버전 변경 후 source sample로 재생성
삭제 speaker 삭제 후 source sample/embedding/metadata 미존재 확인
Cloud payload raw WAV/PCM, source sample, embedding, local path 미포함 확인
로그 raw path/waveform/vector가 logcat/AILog에 남지 않는지 확인
권한 허용되지 않은 앱에서 API 호출 실패
등록 중 WUW/F2 등록 화면 녹음 중 일반 음성명령 씬이 시작되지 않는지 확인
lock release commit/cancel/error/timeout/reboot 후 일반 음성명령이 다시 가능한지 확인
취소/timeout 중간 취소와 앱 종료 후 temp session cleanup
UX 문구 보관/삭제/Cloud 미전송 안내가 사용자에게 노출되는지 확인

6. API 계약 초안

Enrollment API Sequence

6.1 등록 시작

data class SpeakerEnrollmentStartRequest(
    val requestedDisplayName: String,
    val locale: String,
    val sampleCount: Int = 5,
    val consentVersion: String,
    val callerPackage: String
)

data class SpeakerEnrollmentStartResult(
    val enrollmentSessionId: String,
    val requiredSampleCount: Int,
    val samplePolicy: SpeakerSamplePolicy
)

6.2 샘플 제출

data class SpeakerSampleSubmitRequest(
    val enrollmentSessionId: String,
    val sampleIndex: Int,
    val audioUri: String,
    val audioFormat: AudioFormatSpec,
    val deleteLauncherRawAfterImport: Boolean = true
)

data class SpeakerSampleSubmitResult(
    val accepted: Boolean,
    val qualityCode: SpeakerSampleQualityCode,
    val messageCode: String,
    val acceptedSampleCount: Int,
    val requiredSampleCount: Int
)

6.3 등록 완료

data class SpeakerEnrollmentCommitRequest(
    val enrollmentSessionId: String,
    val displayName: String
)

data class SpeakerEnrollmentCommitResult(
    val result: SpeakerEnrollmentResultCode,
    val speakerId: String,
    val displayName: String,
    val embeddingCount: Int,
    val profileState: String = "voice_only",
    val sourceSampleRetained: Boolean = true
)

6.4 조회/삭제

data class RegisteredSpeakerSummary(
    val speakerId: String,
    val displayName: String,
    val createdAt: Long,
    val updatedAt: Long,
    val embeddingCount: Int,
    val profileState: String
)

interface SpeakerEnrollmentApi {
    fun startEnrollment(request: SpeakerEnrollmentStartRequest): SpeakerEnrollmentStartResult
    fun submitSample(request: SpeakerSampleSubmitRequest): SpeakerSampleSubmitResult
    fun commitEnrollment(request: SpeakerEnrollmentCommitRequest): SpeakerEnrollmentCommitResult
    fun cancelEnrollment(enrollmentSessionId: String)
    fun getRegisteredSpeakers(): List<RegisteredSpeakerSummary>
    fun deleteSpeaker(speakerId: String): Boolean
    fun acquireVoiceCommandSceneLock(lock: VoiceCommandSceneLock): Boolean
    fun releaseVoiceCommandSceneLock(ownerSessionId: String): Boolean
    fun getVoiceCommandSceneLockState(): VoiceCommandSceneLock?
}

7. 에러/품질 코드

코드 의미 런처앱 안내
OK 샘플 수락 다음 샘플 녹음
TOO_SHORT 발화 길이 부족 조금 더 길게 말해 주세요
TOO_SILENT 음량 부족/거리 멂 조금 더 가까이에서 말해 주세요
CLIPPING 소리가 찢어짐 조금 더 작게 말해 주세요
NOISE_HIGH 주변 소음 큼 조용한 곳에서 다시 말해 주세요
MULTI_SPEAKER 다화자 감지 한 명씩 말해 주세요
OVERLAP_SPEECH 겹침 발화 가능성 다른 사람이 말하지 않을 때 다시 말해 주세요
DUPLICATE_LIKELY 기존 화자와 유사 이미 등록된 목소리일 수 있습니다
ENGINE_NOT_READY 모델/엔진 초기화 전 잠시 후 다시 시도해 주세요
STORAGE_ERROR 저장 실패 등록에 실패했습니다. 다시 시도해 주세요

1단계에서는 DUPLICATE_LIKELY를 강제 차단하지 않고 확인 안내로 낮춰도 된다. 다만 같은 사람이 중복 등록되는 문제는 운영 품질에 영향을 주므로 score 기반 warning은 남겨야 한다.

8. 저장소와 개인정보 정책

Enrollment Storage Policy

1단계의 핵심은 녹음파일을 방치하지 않는 것이다. 다만 재부팅, 모델 버전 변경, embedding 재생성, 재등록 UX를 고려하면 “등록에 성공한 음성 샘플”은 별도 관리 자산으로 남길 필요가 있다.

따라서 녹음파일은 두 종류로 나눈다.

구분 의미 정책
temporary raw file 품질검사/embedding 추출 전 임시 파일 rejected/canceled/timeout 또는 import 완료 후 삭제
accepted source sample 등록에 성공한 샘플의 보관본 AI 모듈 private storage에 암호화 보관, 재생성/재등록/삭제 정책 적용

즉 “raw audio를 무조건 삭제한다”가 아니라, 런처앱/세션 임시 파일은 삭제하고, accepted source sample은 재등록과 rebuild를 위해 통제된 로컬 저장소에 보관한다가 1단계 정책이다.

Raw Audio Lifecycle

1단계 기본 정책:

8.1 녹음파일 lifecycle 관리

서비스에서 관리해야 하는 대상은 아래와 같다.

관리 대상 설명 보존 여부
enrollmentSessionId 등록 세션 추적 ID 등록 이벤트 기록에 보존 가능
sample index/count 몇 번째 샘플인지, accepted sample 수 metadata 또는 audit에 보존 가능
quality result 무음/짧음/소음/다화자/겹침 등 실패 사유 민감정보 제거 후 보존 가능
temporary raw file 품질검사/embedding 추출 전 임시 파일 commit/cancel/timeout 후 삭제
accepted source sample 등록 성공 후 재생성 가능한 보관 샘플 사용자 삭제 전까지 로컬 암호화 보관
embedding 등록 결과물 사용자 삭제 전까지 보존
speaker metadata speaker_id, 표시 이름, 동의 버전, 생성일 사용자 삭제 전까지 보존
deletion proof temporary raw/source sample/embedding 삭제 처리 결과 audit event로 보존 가능

raw 파일 상태는 아래처럼 명시적으로 둔다. 핵심은 embedded 이후 accepted sample이 source_archived로 승격되고, 세션 임시 파일만 정리된다는 점이다.

recording -> imported -> quality_checked -> embedded -> source_archived -> session_cleaned
recording -> imported -> rejected -> raw_deleted
recording -> imported -> canceled -> raw_deleted
recording -> imported -> timeout -> raw_deleted
source_archived -> rebuild_embedding -> embedding_refreshed
deleteSpeaker -> source_deleted + embedding_deleted + metadata_deleted

권장 로컬 구조:

/data/.../speaker_enrollment/
  sessions/
    {sessionId}/
      manifest.json
      sample_001.wav.tmp
      sample_001.wav.ready
      sample_002.wav.tmp
      sample_002.wav.ready
  speakers/
    {speakerId}/
      embedding.bin
      metadata.json
      source_samples/
        manifest.json
        sample_001.wav.enc
        sample_002.wav.enc
        sample_003.wav.enc
  audit/
    enrollment_events.log

운영 규칙:

8.2 Cloud 보관 정책

1단계에서 raw 녹음파일을 Cloud로 보내는 것은 제외한다. 단, accepted source sample은 재등록과 embedding rebuild를 위해 온디바이스 AI 모듈 private storage에만 보관한다.

데이터 Cloud 전송 사유
raw WAV/PCM 금지 개인 식별 가능성이 높은 원본 음성
accepted source sample 금지 등록 재생성용 원본이므로 Cloud 노출 금지
embedding vector 금지 voiceprint 자체로 민감정보
local file path 금지 내부 저장소 구조 노출
speaker_id 가능 개인화/권한 판단용 summary key
speaker_state/confidence 가능 speaker_context 판단 근거
profile_state 가능 voice_only, linked, verified 같은 상태값

Cloud에는 아래 수준만 전달한다.

{
  "speaker_context": {
    "speaker_id": "spk_001",
    "speaker_state": "identified",
    "speaker_confidence": 0.72,
    "profile_state": "voice_only"
  }
}

권장 저장 구분:

데이터 저장 위치 보존 정책
임시 raw audio 런처앱 cache 또는 AI 모듈 import temp commit/cancel 후 삭제
accepted source sample AI 모듈 private encrypted storage 사용자 삭제 전까지, rebuild/re-registration 용도
embedding AI 모듈 private storage 사용자 삭제 전까지
speaker metadata AI 모듈 private DB/file 사용자 삭제 전까지
registration logs 민감정보 제거 후 event log 운영 정책에 따름
Cloud payload raw audio/embedding 제외 speaker_context summary만

9. 1단계 사용자 흐름

Enrollment User Flow

  1. 사용자가 런처앱에서 “음성 등록”을 누른다.
  2. 런처앱이 음성정보 이용 목적과 삭제 가능성을 안내한다.
  3. 사용자가 동의한다.
  4. 런처앱이 AI 모듈에 startEnrollment()를 호출한다.
  5. 사용자가 안내 문장을 3~5회 말한다.
  6. 런처앱은 각 샘플을 AI 모듈에 submitSample()로 전달한다.
  7. AI 모듈은 품질을 검사하고 accepted/retry를 반환한다.
  8. 충분한 샘플이 모이면 commitEnrollment()를 호출한다.
  9. AI 모듈은 embedding을 저장하고 speaker_id를 반환한다.
  10. 런처앱은 등록 완료 화면과 삭제/이름 변경 진입점을 제공한다.

10. 서비스 1단계 완료 기준

항목 완료 기준
런처앱 연동 런처앱에서 등록 시작/샘플 제출/완료/취소/삭제 호출 가능
품질 검사 무음/짧음/소음/다화자/겹침/저음량 케이스가 코드로 반환
등록 엔진 3개 이상 accepted sample로 speaker_id 생성 및 embedding 저장
식별 연계 등록 후 일반 발화에서 speaker_context.speaker_id 후보 생성
삭제 speaker_id 삭제 시 embedding/profile metadata 삭제
raw audio 정책 임시 raw audio 삭제, accepted source sample 로컬 암호화 보관/삭제
권한 런처앱 signature permission 또는 동등한 호출자 검증
로그 raw audio path/embedding 미노출, 결과 코드 중심 로그
테스트 등록 성공/품질 실패/삭제/재시작 후 로드/식별 smoke test

11. 제품화 전 추가 보강 체크리스트

Remaining Considerations

현재 1단계 구조는 “런처앱 UX + 온디바이스 AI 모듈 등록 엔진 + 로컬 보관 + Cloud summary 전송 금지”까지 정리되어 있다. 다만 제품화 직전에는 아래 항목을 별도 게이트로 닫아야 한다. 이 항목들은 기능 화면을 만드는 작업보다 덜 눈에 띄지만, 나중에 모델 변경, 재부팅, 삭제, SQE 검증, 개인정보 검토에서 문제가 되는 영역이다.

영역 반드시 정리할 내용 판단
Embedding 파일 계약 embedding.bin의 포맷, dim, count, endian, 모델 fingerprint, checksum, 암호화 방식 Must
Threshold/품질 보정 등록 pass/fail 기준, duplicate likely 기준, 소음/거리/짧은 발화별 threshold Must
복구 상태 pending deletion, rebuild required, source missing, stale voice lock, migration failed 상태 Must
개인정보 운영 backup 제외, factory reset, consent revoke, 사용자 삭제 시 source/embedding/history 동시 삭제 Must
관측성 등록 결과 코드, 품질 실패 분포, lock timeout, rebuild 결과 로그 Must
책임 경계 런처앱 UI owner, AI 모듈 API owner, SoC/Cloud boundary owner Must
확장성 Voice ID와 Face ID/앱 계정/Google 계정 link 후보 관리 Should

11.1 Embedding 파일 계약

현재 PoC는 임베딩을 메모리에 올려 쓰는 성격이 강하다. 제품화에서는 재부팅 후에도 등록 상태를 유지해야 하므로 embedding 저장 포맷을 명시해야 한다.

권장 metadata:

{
  "speaker_id": "spk_001",
  "display_name": "Yongjae",
  "model_id": "embedding.onnx",
  "model_version": "2026-07",
  "embedding_dim": 256,
  "embedding_count": 5,
  "sample_rate": 16000,
  "format": "float32_le",
  "checksum": "sha256:...",
  "encrypted": true,
  "source_sample_count": 5,
  "created_at": 1783420000000,
  "updated_at": 1783420000000
}

정책:

11.2 Threshold/품질 보정

화자 등록은 “등록 성공”보다 “오등록을 막는 것”이 중요하다. threshold는 단일 숫자로 끝내지 말고 품질 코드와 함께 운영해야 한다.

기준 목적 예시
enrollment quality threshold 등록 샘플 자체 품질 판단 너무 짧음, 무음, clipping, 소음
speaker match threshold 등록 화자 식별 pass 기준 score가 기준 이상일 때 identified
ambiguity margin 1등/2등 화자 점수 차이 점수 차이가 작으면 ambiguous
duplicate likely threshold 기존 화자와 유사한 신규 등록 방지 “이미 등록된 목소리일 수 있음”
environment policy 거리/소음/팬소음 조건 조용한 환경에서 재시도 안내

운영 관점에서는 threshold를 코드 상수로만 두지 말고, 모델 버전과 함께 추적해야 한다. 모델이 바뀌면 같은 threshold라도 false accept/false reject 성격이 바뀔 수 있다.

11.3 복구 상태

제품에서는 정상 flow보다 비정상 종료 처리가 중요하다.

상태 발생 조건 처리
pending_deletion source sample 또는 embedding 삭제 일부 실패 다음 부팅/idle worker에서 재시도
rebuild_required embedding 파일 없음, 모델 버전 변경 accepted source sample로 재생성
source_missing embedding은 있으나 source sample 없음 식별은 가능하되 모델 migration 불가 상태로 표시
lock_stale 등록 중 앱 crash로 VoiceCommandSceneLock 유지 timeout 후 lock 해제
migration_failed 포맷 변경 또는 파일 손상 재녹음 안내 또는 기존 profile 비활성화

특히 VoiceCommandSceneLock은 등록 중 일반 음성명령 진입을 막기 때문에 stale 상태가 길게 남으면 제품 사용성이 크게 깨진다. lock에는 반드시 TTL과 ownerSessionId 검증이 필요하다.

11.4 개인정보/운영 정책

화자인식 데이터는 일반 설정값이 아니라 식별 가능한 생체성 데이터에 가깝다. 제품 문구와 삭제 정책이 기술 구현과 맞아야 한다.

필수 운영 정책:

11.5 관측성

로그가 없으면 SQE/필드에서 “등록이 잘 안 된다”를 분석할 수 없고, 로그가 과하면 개인정보 리스크가 생긴다. 따라서 결과 코드 중심으로 남긴다.

권장 event:

speaker_enrollment_started
speaker_sample_accepted
speaker_sample_rejected(qualityCode)
speaker_enrollment_committed(resultCode, embeddingCount)
speaker_enrollment_canceled(reason)
speaker_deleted(resultCode)
speaker_embedding_rebuilt(resultCode)
speaker_voice_command_lock_timeout

금지:

11.6 책임 경계

제품화 전에는 담당 경계를 아래처럼 고정해야 한다.

책임 Owner 산출물
등록 화면/문구/동의 UX 런처앱 화면, 문구, UX state
등록 API/품질 코드 온디바이스 AI 모듈 AIDL/Bound Service 계약
embedding 추출/저장/삭제 온디바이스 AI 모듈 SpeakerEmbeddingStore, manifest
일반 음성명령 lock 온디바이스 AI 모듈 + 런처앱 VoiceCommandSceneLock 계약
개인정보 정책 제품/법무/개발 공동 consent, 삭제, backup 제외
Cloud 연동 Cloud LLM/온디바이스 공동 speaker_context summary 계약
실기기 검증 QA/SQE 등록/재부팅/삭제/lock/rebuild evidence

정리하면, 추가로 봐야 할 핵심은 “화자를 등록할 수 있느냐”가 아니라 등록 데이터가 재부팅/삭제/모델 변경/개인정보 검토/필드 분석 상황에서도 일관되게 관리되느냐다.

12. 후속 단계와 연결

1단계 산출물은 이후 아래 단계의 기반이 된다.

후속 단계 1단계에서 준비해야 하는 것
speaker_context 제품 적용 speaker_id, confidence, state를 안정적으로 생성
개인화 memory speaker_id를 person profile 후보로 연결
바이탈 Face ID link Voice ID를 직접 face id에 묶지 않고 Person Profile link로 확장
앱 계정 link 계정 이름과 voice_id를 1:1로 강제하지 않고 사용자 승인 기반 연결
Google account link OAuth account는 외부 identity scope로 분리
권한/가족 모델 speaker_id별 authority level 후보 관리

즉 1단계의 핵심은 “목소리를 등록한다”가 아니라, 이후 서비스 확장을 깨지 않도록 Voice ID 등록 경로, 저장/삭제 정책, 런처앱 연동 계약을 제품 구조로 고정하는 것이다.

Keyboard shortcuts

⌘K / Ctrl+KOpen command palette
/Focus search
g hGo to home
g pGo to projects
g sGo to sessions
j / kNext / prev row (tables)
?Show this help
EscClose dialogs

Structured queries

Mix key:value filters with free text in the palette:

type:sessionOnly session pages
project:llm-wikiFilter by project name (substring)
model:claudeFilter by model name (substring)
date:>2026-03-01Sessions after a date
date:<2026-04-01Sessions before a date
tags:rustPages mentioning a tag/topic
sort:dateSort results by date (newest first)

Example: type:session project:llm-wiki date:>2026-04 sort:date