Files

15 KiB
Raw Permalink Blame History

Find-Word v0.1.1 — 한국어 neural voice 업그레이드 결과

작성일: 2026-10-14 UTC. worktree: /home/hermes/worktrees/find-word-v0.1.1. 브랜치: fix/v0.1.1-neural-voice. 입력 .agent/TASK-v0.1.1.md, .agent/ACCEPTANCE-v0.1.1.md 및 기존 맥락을 읽었다. 입력 문서는 수정하지 않았다. Git commit/push, main merge, 서버 배포는 실행하지 않았다.

구현 결과

  • package·lock·UI·index·README·LICENSE·현재 버전 문서·manifest를 0.1.1로 맞췄다.
  • 정확한 크레딧 Designed by OZ at Heat-T Medical Inc.와 canonical repository https://source.ibit.kr/OZ-Group/Find-Word를 유지했다.
  • Microsoft Edge online TTS, ko-KR-HyunsuMultilingualNeural, edge-tts 7.3.1, rate -6%, pitch -4Hz로 안내 1개·목표 100개를 모두 실제로 새로 생성했다. 기존 WAV를 재포장하거나 재사용하지 않았다.
  • pinned .venv-audio/bin/python과 requirements-audio.txt로 생성 절차를 고정했다. 전역 Python의 다른 버전으로 fallback하지 않는다. FFmpeg 실측 버전은 6.1.1-3ubuntu5이다.
  • 임시 MP3를 FFmpeg로 24kHz mono PCM16으로 디코드하고 선형 gain만 적용한다. 파일별 RMS 목표 0.10, 증폭 최대 2배, peak ceiling 0.89. 압축·게이트·음절 편집은 없다.
  • 임시 디렉터리에서 101개 전체 검증 후 Linux renameat2(RENAME_EXCHANGE)로 단일 원자 교환한다. provider 최대 3회, 1/2초 backoff, 시도당 60초 제한. 변환 실패·검증 실패·중단·원자 교체 실패는 기존 자산을 보존한다.
  • manifest에 원문·SHA-256·bytes·duration·RMS·peak와 provider·voice·prosody·버전· sample rate·생성 시각·실측 도구 버전을 남겼다. 인증 헤더·쿠키·API 키는 저장하지 않는다.
  • 기존 생성 도구의 캐시/다운로드/문서/manifest 의존을 제거했다. 기존 v0.1 기록은 보존했다.
  • 10개 mechanic, 100문장, 8환경, 5SNR, 객관식·주관식, IME·재생 취소·재청취· 실패 복구 흐름은 기존 소스와 테스트를 보존했다. 앱은 로컬 WAV만 요청한다.

구현 전에 확인한 실제 RED → GREEN

RED를 모두 실행한 뒤 구현했다. 생성기의 새 함수 계약은 NotImplementedError만 발생시키는 비동작 stub으로 선언했고, 기존 명령 진입점은 RED 때 그대로 두어 import 부작용도 실패로 확인했다. stub에 성공 경로를 미리 구현하지 않았다. RED는 각 명령 종료 코드 1, GREEN은 0이다. 실패 로그를 덮어쓰지 않았다.

계약 실제 RED 실제 GREEN 원문 evidence
UI/package/lock/index/문서 버전 및 pinned 생성·권리 문서 npm test -- tests/neural-release.test.ts tests/App.test.tsx -t '0.1.1|브랜드|고정 venv': 3 failed, 기존 12개는 필터로 제외 동일 명령 3 passed RED, GREEN
neural provider/voice/prosody/실측 버전, 101개 PCM·원문·hash·bytes·duration npm test -- tests/assets.test.ts: 2 failed. 기존 16kHz·누락 metadata/duration을 실제 검출 최종 전체 suite에서 두 계약 모두 통과 RED, GREEN
원자 교체·초기 공개·bounded retry·timeout·기존 자산 보존·secret redaction·실제 MP3 변환·PCM 검증·import 부작용 Python unittest 16개 모두 RED (subtest 포함 failures=4, errors=20) 아래 추가 계약을 포함한 18개 통과 RED, GREEN
앞서 생성한 파일 손상 감지, 다른 edge-tts 버전의 외부 요청 전 거부 구현 전 전체 18개 RED (subtest 포함 failures=5, errors=21) python3 -m unittest discover -s tests/python -v: 18 passed 추가 RED, GREEN

Python 계약은 fake provider/작은 PCM fixture로 실패를 강제한다. 원자 디렉터리 교체와 FFmpeg MP3 변환은 테스트에서도 실제 실행한다. 이 fixture는 public 음성에 사용하지 않았다. 실제 provider의 101개 생성은 다음 별도 로그에 있으며 실패/재시도는 0회였다. 전체 npm test는 Python 계약 runner도 포함하며 skip 없이 통과했다.

실제 음성 생성·전수 독립 검사

명령: npm run audio:generate. 생성 완료: 2026-10-14T06:55:41.751382+00:00. 101개 실제 생성 로그.

항목 실제 결과
WAV 개수 101개, 101개 해시 모두 고유하고 v0.1의 같은 파일과 모두 다름
WAV 합계 34,898,524바이트 (약 33.282MiB), 48MiB 예산 이내
PCM 전부 24,000Hz · mono · signed 16-bit PCM
duration 3.816–11.256초, 안내 7.056초
RMS 범위 0.0999998534–0.1000000486
peak 범위 0.3692016602–0.6843261719, full-scale 0개
원문·bytes·duration·SHA-256 101/101 일치
FFprobe 포맷 확인 + FFmpeg 전체 디코드 101/101 성공
Chrome 실제 WAV 디코드·해시·레벨 검사 101/101 성공, 외부 페이지 요청 0·JS 예외 0
남은 임시 MP3/공개 staging 폴더 0개

독립 검사 스크립트는 생성기의 검증 함수를 import하지 않는다. Python wave·해시·앱 원문과 FFprobe/FFmpeg를 별도로 대조한다. 원문 로그, 파일별 수치 JSON.

Chrome 전수 검사 스크립트는 production preview의 WAV를 실제 OfflineAudioContext로 디코드한다. 응답 bytes/hash, 채널, rate, duration, RMS, peak와 UI/index 버전을 검사한다. Chrome 로그, 101개 디코드 결과 및 sandbox 상태.

청취용: intro.wav, luck-apple.wav.

최종 검증

환경: Node 26.7.0, npm 11.19.0, Python 3.11.16, Chrome 146.0.7680.80, Linux 6.8.0, glibc 2.39. 실측 환경과 pip check.

검증 결과 evidence
npm ci PASS, 238 packages 설치 로그
npm run lint PASS, ESLint + TypeScript strict 최종 로그
npm test 11 files / 60 passed, skip 0; Python 18개 계약 포함 로그
npm run audio:generate 101개 신규 생성·검증·원자 교체 성공 로그
npm run build PASS, 101개 해시·런타임 MIT notice 전문 검증 로그
sandboxed system Chrome desktop/mobile suite 12 passed (2.3m), skip 0 로그
sandboxed Chrome 101개 전수 디코드 PASS, Namespace/PID/Network/Seccomp sandbox 활성 로그
npm audit --audit-level=high 0 vulnerabilities, npm 개발 의존성 포함 로그
독립 101개 원문/hash/PCM/FFmpeg 검사 PASS JSON
소스·문서·manifest 비밀값 패턴 검사 40개 텍스트 파일, 0 matches 기록
git diff --check·문서 링크·sandbox 실행 인자·HEAD·역사 기록 최종 검사 PASS 최종 무결성
과거 evidence + RESULT 해시 대조 115개 전부 동일 baseline, 검사

브라우저 전체 명령: env -u PLAYWRIGHT_NO_SANDBOX CHROME_PATH=/usr/bin/google-chrome DEBUG=pw:browser npm run test:browser. 두 viewport(1280×1000, 360×800)에서 실제 음성으로 10문제 완주·재청취·404 재시도· 키보드·레이아웃·reduced motion·axe·크레딧·MIT HTTP 응답이 모두 통과했다. 시작·게임·결과 axe 위반 0, 외부 페이지 요청 0, 페이지 JS 예외 0이다. 로컬 preview만 테스트 중 열었으며 서버 배포는 하지 않았다. Chrome 전수 검사에서는 chrome://sandbox의 Namespace·Seccomp 상태도 실제 확인했다. headless 브라우저는 실제 파일을 디코드하고 Web Audio를 실행하지만 물리적 스피커 청취를 대신하지 않는다. Chrome stderr의 시스템 GPU/GCM 진단과 페이지 JS 예외는 구분한다.

스크린샷: desktop 시작, 게임, 결과, mobile 시작, 게임, 결과.

변경 파일과 기록 보존

  • 앱 표기: src/App.tsx, index.html, package.json, package-lock.json.
  • 생성: scripts/generate_audio.py, 신규 scripts/neural_audio.py, requirements-audio.txt, .gitignore, eslint.config.js의 venv 제외.
  • 자산: public/audio/*.wav 101개와 manifest.json.
  • 계약 테스트: tests/assets.test.ts, tests/App.test.tsx, 신규 tests/neural-release.test.ts, tests/generator.test.ts, tests/python/test_neural_audio.py.
  • 기존 browser tests는 새 스크린샷 경로와 v0.1.1 실측 길이 주석만 변경했다. tests/browser/game.spec.ts, tests/browser/ui.spec.ts. 기존 assertion/timeout은 유지했다.
  • 문서: README.md, LICENSE, docs/AUDIO_PROVENANCE.md, docs/ARCHITECTURE.md, docs/DEPLOYMENT.md.
  • 이번 기록: .agent/RESULT-v0.1.1.md, .agent/evidence-v0.1.1/.

이전 .agent/RESULT.md, .agent/evidence/의 파일은 수정하지 않았다. 스크린샷도 새 경로에만 저장한다. 입력 TASK/ACCEPTANCE의 시작 상태는 untracked였으며 Codex가 새로 작성한 입력 문서가 아니다. 전체 파일 목록은 changed-files.txt에 별도로 남긴다.

남은 청감·provider 한계

  • 자동 검사와 실제 브라우저 재생은 완료했지만 사람의 청감 평가를 수행한 것은 아니다. 한국어 발음·유사 자모 구분·웃음·소음 혼합 난이도는 대표 샘플과 게임으로 직접 평가해야 한다.
  • 원격 모델/서비스는 고정할 수 없다. pinned client로 절차를 재현해도 다음 생성의 바이트가 같다는 보장은 없다. provider 장애·제한·음성 목록 변경 시 명확히 실패한다.
  • edge-tts의 LGPLv3(일부 MIT)와 FFmpeg의 빌드별 LGPL/GPL은 provider 서비스 조건 및 음성 출력의 권리와 별개다. 생성 음성의 독점 권리나 포괄적 상업 이용 허락을 주장하지 않는다. 근거 링크·확인일·조건의 한계는 AUDIO_PROVENANCE에 기록했다.
  • 원자 교체는 Linux와 해당 파일시스템의 지원이 필요하다. SIGKILL/전원 손실 때 임시 숨김 폴더는 남을 수 있고 전원 손실 내구성까지 보장하지 않는다. 일반 실패·Ctrl-C는 정리한다.
  • 실제 모바일 Safari/물리적 오디오 장치 청취는 이번 Chrome 검증 범위 밖이다.

2026-10-14 추가 — 배포 후 오디오 캐시 회귀 수정

제품 버전 0.1.1을 유지하면서 intro와 목표 100개 WAV 모두 ?v=0.1.1을 붙이도록 수정했다. src/audio/assets.ts의 AUDIO_ASSET_VERSION과 audioAssetUrl을 출제·오디오 엔진이 함께 사용한다. WAV/manifest 및 package/lock은 변경하지 않았다.

발견 원인은 음성 내용 교체 후에도 요청 URL이 같았던 것이다. 사용자 보고의 배포 Nginx /audio 7일 캐시에서 기존 사용자는 이전 음성을 계속 받을 수 있었다. 기존 배포 문서의 재검증 가정과 실제 설정이 달랐으며, 브라우저 테스트의 전체 URL .endsWith('.wav')와 404 glob도 query string을 놓쳤다. 원격 서버에는 접속하지 않고 소스·로컬 production build에서 재현했다.

앱 구현을 수정하기 전에 unit/browser 계약을 작성하고 실제 RED를 확인했다. RED 때 앱 소스가 그대로였다는 기록은 04-red-state.json에 있다.

계약/검증 결과 새 evidence
목표 100개 버전·intro/target fetch·각각 404 retry·query pathname 판정 RED 5 failed / 12 passed → GREEN 17 passed RED, GREEN
실제 브라우저 요청/정답 mapping·intro/target 404·101개 URL 구현 전 desktop RED 4 failed → 전체 desktop/mobile GREEN 16 passed (2.6m) RED, GREEN
npm run lint PASS 로그
npm test 12 files / 65 passed, skip 0; Python 18개 계약 포함 로그
npm run build PASS, 101개 WAV·notice 검증 로그
실제 Chrome sandbox Namespace·PID·Network·Seccomp-BPF 활성, --no-sandbox 없음 로그
query URL 전수 Chrome 해시·bytes·24kHz mono 디코드·duration·RMS·peak 두 viewport 각각 101/101, 외부 요청 0·JS 예외 0 desktop, mobile
과거 evidence·자산·제품 버전 파일 보존 및 HEAD·diff 검사 254개 해시 동일, 과거 evidence 149개 포함 최종 무결성

전체 브라우저 명령은 env -u PLAYWRIGHT_NO_SANDBOX CHROME_PATH=/usr/bin/google-chrome DEBUG=pw:browser npm run test:browser이다. 실제 재생·재청취·10문제 완주·404 복구 후 정답·접근성 검증을 유지했다. 101개 query URL 검사를 tests/browser/audio.spec.ts로 정규 suite에 편입했고, URL 판정 helper의 query 순서·가짜 WAV 접미사도 단위 테스트로 고정했다. 런타임 외부 요청이나 manifest 조회는 추가하지 않았다.

브라우저 스크린샷은 이제 test-results/를 사용하며 이번 결과는 새 artifacts 경로에 복사했다. 과거 evidence와 기존 이 보고서 본문은 보존하고 이번 기록만 추가했다. 보조 sandbox probe의 최초 라벨 판정 오류와 수정 결과도 원인·RED/GREEN·최종 결과 로그에 기록했다.

README·ARCHITECTURE·DEPLOYMENT에 자산 버전 갱신 규칙과 7일 캐시 예시를 반영했다. 이 변경의 검증 대상은 로컬 build이며 Git commit/push/tag/merge와 서버 배포는 실행하지 않았다.