8월 7일까지 워킹트리에만 남아 있던 미커밋 작업을 커밋한다. 여러 사본 폴더(worktree·clone)에 흩어져 있던 중간 스냅샷을 정리하기 전에 원본을 git 이력으로 고정하는 것이 목적이다. - contracts/routes/services: measurement, outcome_trajectory, rupture_repair, deliberate_practice, calibration_transfer, supervision_research, multimodal_alliance, continuous_improvement 계열 신규 모듈과 테스트 - infra/db/init: 07~16 마이그레이션(측정 기반~calibration transfer 실행) - apps/web: 세션 리뷰 카드·관리 화면·E2E 스펙 추가 - docs/ops: G0~G8 라이브 통합·배포·롤백 증거 문서와 evidence JSON/PNG - scripts: smoke·ledger·릴리스 에이전트·NAS 프리뷰 운영 스크립트 engine.public 로그 .bak과 apps/web/test-results 산출물은 커밋에서 제외했다.
15 KiB
G7 로컬 음성 WebSocket 내구성 증거 — 2026-08-07
판정
로컬 loopback Uvicorn 서버가 실제 제품 라우터의 /voice/ws를 제공하도록 구성하고,
결정적 synthetic PCM으로 짧은 WebSocket soak를 통과했다. 이 결과는 물리 마이크,
공개 WSS, 실제 PostgreSQL 동의 원장, 외부 STT/TTS/LLM provider의 장시간 안정성 증거가 아니다.
- 실행기:
scripts/soak-voice-websocket.py - 실제 통과한 경로: Uvicorn WebSocket transport → FastAPI router →
/voice/ws - 짧은 실행 시간: 요청
8초, 실제 timed soak8.015초 - 종료 코드:
0(전 계약 통과) - 50분 옵션:
--duration-seconds 3000을 제공하지만 이번 검증에서는 실행하지 않았다.
실제 경로와 override 경계
실제 코드로 통과한 부분은 WebSocket accept/close, 인증·역할 분기, session bind 이후 ready frame,
ping/pong, audio_start/binary/audio_end, PCM→WAV 정규화, 발화마다 수행하는 G7 consent
preflight, transcript/reply/TTS frame 순서, 발화 byte cap, overflow 뒤 동일 연결 복구, close code다.
외부 비용과 비결정성을 제거하기 위해 아래 경계만 명시적으로 override했다.
_principal_from_websocket: 승인된 synthetic learner 또는 인증 거부 상태_bind_session: synthetic 기존 회기 ID와 고정 voice presetmultimodal_alliance_store.assert_voice_processing_allowed: 회기별 in-memorygranted/withdrawn/미동의 gatevoice_service.transcribe: 입력이 실제 WAV 정규화를 통과했는지 검사한 뒤 고정 transcript 반환_run_turn_and_speak: 고정 reply와 1개 synthetic binary TTS frame 반환voice_service.is_available: provider 정상/degraded 종료 분기를 결정적으로 전환_MAX_AUDIO_BYTES: overflow를 빠르게 검증하기 위해 운영10MiB를 실행 중에만4KiB로 축소
따라서 consent grant/withdraw 검증은 실제 DB의 consent HTTP API를 호출한 결과가 아니라, 제품 voice route가 참조하는 store 경계의 상태를 결정적으로 전환한 결과다. 다만 철회 전 첫 발화는 synthetic STT seam까지 1회 도달하고, 같은 socket에서 철회한 뒤의 발화와 철회 상태로 재연결한 발화는 둘 다 STT 호출 수를 늘리지 않아 처리 전 fail-closed를 확인했다.
짧은 soak 결과
| 항목 | 결과 |
|---|---|
| 연결 열림 / 완료 | 83 / 83 |
정상 close 1000 |
81 |
미인증 close 1008 |
1 |
provider degraded close 1011 |
1 |
| 성공 synthetic 발화 | 79 |
| synthetic STT / TTS frame | 79 / 79 |
| 유료 provider 호출 | 0 |
| grant 상태 동일 회기 재연결 | 통과 |
| 같은 socket 철회 후 STT 이전 차단 | 통과 |
| 철회 상태 동일 회기 재연결 차단 | 통과 |
| 미동의 회기 차단 | 통과 |
| overflow 거부 / 이후 복구 | 1 / 1 |
| 예기치 않은 오류 | 0 |
종료 코드별 계약은 인증 거부 1008, provider 준비 실패 1011, 정상 client close 1000으로
확인했다. 모든 연결이 완료되어 러너 관점의 미종료 WebSocket은 없었다.
bounded queue와 backpressure에 대한 정확한 범위
러너의 Uvicorn/WebSockets inbound queue는 ws_max_queue=4로 제한했다. 클라이언트는 수신을
기다리지 않고 1KiB frame 5개를 연속 전송했고, 제품 라우터의 발화 buffer cap은
audio too large; please send a shorter utterance로 이를 거부했다. overflow payload는 synthetic
STT seam에 도달하지 않았으며, 같은 socket에서 다음 정상 발화가 성공했다.
후속 완료감사에서 route-owned audio buffer는 chunk를 붙이기 전에 운영 10MiB 상한을 검사하도록
바꿨다. transcript event queue는 32, Deepgram client receive queue는 16, write limit은 64KiB로
고정했다. queue가 찼을 때 event producer의 await put()이 실제로 대기하고 consumer가 한 건을 꺼낸 뒤
재개되는 회귀도 통과했다.
이 증거가 말할 수 있는 범위는 다음과 같다.
- loopback transport queue를 명시적으로 bounded 설정한 상태에서 burst와 재연결이 hang 없이 완료됨
- 애플리케이션 발화 buffer가 상한 초과 chunk를 append 전에 버리고, provider 호출 없이 idle→다음 발화로 복구함
- route transcript event와 Deepgram transport queue가 유한하며 queue 포화 시 producer에 backpressure를 적용함
- 단일 Python 프로세스에서 운영 10MiB buffer high-water와 초과 1 byte 거부를 직접 실행함. 이때 RSS는
77,541,376→88,092,672 bytes, process peak 증분은20,279,296 bytes, clear 뒤77,602,816 bytes였다.
반대로 이 실행은 실제 운영 worker 수에서의 동시 connection RSS/CPU, Uvicorn 내부 queue high-water mark, OS socket buffer, Cloudflare/proxy queue를 측정하지 않았다. 따라서 단일 프로세스·단일 buffer 수치를 전체 운영 topology의 절대 메모리 상한이나 50분 backpressure 증명으로 확대하지 않는다.
실행 명령
이번에 실제 실행한 짧은 모드:
C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe -X utf8 `
scripts\soak-voice-websocket.py
선택적 50분 모드(이번에는 실행하지 않음):
C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe -X utf8 `
scripts\soak-voice-websocket.py --duration-seconds 3000
CLI 종료 코드는 전 계약 통과 0, 계약/transport 실패 1, 잘못된 인자 2다.
관련 회귀
C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe -X utf8 -m pytest `
-p no:cacheprovider `
apps/api/app/test_voice_ws.py `
apps/api/app/test_multimodal_alliance_store.py `
apps/api/app/test_multimodal_alliance.py -q
결과: 42 passed, Starlette python_multipart 전환 관련 기존 경고 1건.
test_voice_ws.py에는 한 socket의 첫 PCM 발화는 STT까지 도달하지만, 두 번째 발화 직전에
consent가 철회되면 multimodal_consent_withdrawn 오류와 idle만 보내고 STT를 다시 호출하지 않는
회귀를 추가했다.
같은 Python 3.11로 apps/api/app과 apps/api/engine_gateway 전체 회귀도 실행했고
805 passed, 동일한 기존 경고 1건으로 통과했다.
내부 완료감사 추가 증거 — 2026-08-07
실제 PostgreSQL 동시 철회
scripts/smoke-multimodal-consent-concurrency.py를 개발 PostgreSQL vignette-dev-db에 대해 실행했다.
provider·마이크·로그인 없이 고유 synthetic learner/session을 만들고, 철회 transaction이 session 공통
consent lock을 잡은 상태에서 evaluator timeline write를 동시에 시작했다.
- 철회 미커밋 동안 timeline writer 대기:
true - 철회 커밋 뒤 writer fail-closed:
true - 해당 submission의 timeline row:
0 - 이후 voice processing 차단:
true - 실제 lock 구간 포함 elapsed:
281ms
따라서 이전 목록의 “실제 PostgreSQL grant/withdraw 동시성”은 내부 저장 경계에서 닫혔다. 다만 열린 public WebSocket과 DB 철회를 함께 실행하는 공개 장시간 동시성은 50분 외부 gate에 남는다.
text-only 대비 calibrated voice gain
기존 benchmark는 text_only_accuracy와 fused_accuracy를 fixture에 직접 적고 fusion decision만 맞췄기 때문에
추가 이득을 측정한 증거가 아니었다. 각 case에 독립 target_value를 추가하고 실제 출력의
one_minus_mean_absolute_error를 계산하도록 수정했다.
- text-only:
0.9533333333 - calibrated multimodal:
0.9673333333 - measured incremental gain:
+0.014 - minimum gain:
0.01 - synthetic case:
3, fusion decision accuracy:1.0 - target을 text prediction으로 바꾸는 반례: gain
<0, gate 실패
이는 synthetic educational gold label에 대한 알고리즘 회귀다. 독립 평가자가 라벨링한 held-out 음성이나 실제 수련생 pilot에서의 효과성·일반화 증거는 아니므로 외부 종료 gate에 별도로 남긴다.
후속 loopback cap/RSS 실행
scripts/soak-voice-websocket.py --duration-seconds 3 --cycle-interval-ms 50 결과는 연결 61/61, 정상 close
59, 성공 발화/STT/TTS 57/57/57, 철회 차단 2, 미동의 차단 1, overflow 거부/복구 1/1,
예기치 않은 오류 0으로 통과했다. 이 실행에서 cap 초과 뒤 idle 미복귀 결함을 먼저 재현했고, 오류 뒤
state=idle을 보내 동일 socket의 다음 발화가 성공하도록 수정한 뒤 재통과했다.
완료감사 후 Python 3.11 focused voice/G7 회귀는 78 passed, API app 전체는 782 passed, Ruff 대상 파일은
All checks passed, SSOT checker는 passed:true였다. Starlette python_multipart 전환 경고 1건은 기존 경고다.
아직 남은 외부 증거
- 실제 Deepgram 운영 key·quota를 사용한 interim/final·word timestamp live 수신
- 기대 STT/TTS provider·model 네 값과 정확히 일치하는 authenticated public WSS
ready - 사용자가 실행 직전에 명시 동의한 물리 마이크 capture와 브라우저
MediaRecorder장시간 운전 - 외부 STT/TTS provider의 50분 양방향 비용·rate limit·장애 복구
- 운영 TTS provider/model의 실제 배포 설정 증거
- 공개 TLS/WSS, Cloudflare idle timeout, 프록시 buffering과 네트워크 단절
- 독립 라벨 held-out/pilot에서 text-only 대비 실제 음성 추가 이득
- 실제 worker 수와 Uvicorn·OS·Cloudflare queue를 포함한 RSS/CPU/queue high-water 관측
이 항목들은 이번 로컬 synthetic soak로 DONE 처리할 수 없다.
공개 배포 뒤 외부 감사
2026-08-07 공개 배포 후 TLS 1.3/Cloudflare를 거쳐 /voice/ws handshake에 도달했고 비인증 요청은
1008 not authenticated로 닫혔다. 저장된 public auth state와 소유 voice session이 없어 authenticated
ready와 50분 soak는 시작하지 않았다.
후속 하드닝에서 scripts/soak-public-voice-websocket.py의 증거 계약을 v3로 올렸다. --preflight-only는
실제 auth cookie·소유 회기로 public WSS ready/ping만 확인하고, 필수 인자인 기대 STT/TTS provider/model
네 값과 서버 메타데이터를 정확히 비교한다. 이 모드는 마이크 장치 열거와 캡처를 전혀 수행하지 않으므로
물리 마이크 증거가 아니다. full soak는 transcript/reply/TTS binary/idle을 요구하고 기본 3000초로 실행되며,
auth cookie·소유 회기·--confirm-physical-capture를 모두 확인한 뒤에만 장치를 열거하고 캡처한다.
비밀값, session ID, 축어록, 응답 payload와 원음은 증거에 기록하지 않는다.
같은 후속 코드 범위에서 Deepgram streaming adapter가 interim/final, word timestamp, provider event를 처리하고,
ready가 실제 STT/TTS provider/model을 반환하도록 구현했다. streaming 중 동의는 1초마다 재검사하며 철회 시
추가 오디오 전송과 후속 저장을 중단한다. G7 word token은 deployment SESSION_SECRET을 키로 한
HMAC-SHA256 pseudonym만 저장하고 원문 word는 저장하지 않는다. 프론트는 네트워크/TTS 단절 뒤 작성 중 텍스트를
보존하고 텍스트 계속하기 또는 키보드 가능한 음성 재연결을 제공한다. 이는 모두 코드/내부 회귀 증거이며
Deepgram live, authenticated public ready 또는 물리 마이크 증거가 아니다.
G7 voice hardening 공개 재배포 — 2026-08-07 04:31~04:38 KST
voice hardening release-only patch 3197805058e3…2bc를 clean worktree에 적용해 공개 API와 Web을
재배포했다. API 8001은 새 clean worktree CWD에서 실행되며 local/public /health가 prod·DB·engine green,
/voice/health가 다음 실제 운영 설정을 반환했다.
| 메타데이터 | 실제값 |
|---|---|
| STT provider / model | openai / gpt-4o-transcribe |
| STT batch fallback | true |
| TTS provider / model | openai / gpt-4o-mini-tts |
운영 런타임에는 Deepgram 자격증명이 주입되지 않아 streaming provider가 활성화되지 않았다. 따라서 위 STT
metadata는 정상적인 OpenAI batch fallback 증거이지 Deepgram interim/final·word timestamp live 증거가 아니다.
인증 없는 공개 WSS는 error frame 뒤 1008로 닫혔고, 저장된 공개 인증 상태와 소유 회기가 없어 authenticated
ready/ping preflight는 실행하지 않았다. 이 확인 과정은 마이크 장치 열거·캡처를 전혀 수행하지 않았다.
Pages production 6d7e89d5와 custom domain은 새 entry와 Session JS/CSS를 올바른 MIME으로 제공하고,
Session 청크에 AI 생성 음성 고지 문구가 포함된 것을 확인했다. watchdog도 새 clean worktree로 재등록해
LastTaskResult=0을 통과했다. 공개 코드와 운영 TTS 설정은 승격됐지만, G7 종료에는 여전히 실제 Deepgram
key·quota live, expected metadata authenticated public ready, 명시 동의 물리 마이크와 50분 soak가 모두 필요하다.
운영 TTS 권리·고지 경계
운영 TTS는 OpenAI API gpt-4o-mini-tts와 built-in voice만 사용한다. 2026-08-07에 확인한
OpenAI Services Agreement 2.2는 API를 고객 애플리케이션에
통합해 최종 사용자에게 제공할 권리를, 4.1은 법이 허용하는 범위에서 출력 소유를 고객에게 둔다. 이는 서비스
계정이 해당 약관과 적용 정책을 준수한다는 전제의 제품 근거이며 별도 법률 의견으로 확대하지 않는다.
OpenAI Text-to-Speech 가이드는 최종 사용자에게
듣는 음성이 AI 생성이며 사람 음성이 아니라는 명확한 고지를 요구한다. Session UI는 같은 문장을 회기 시작 전,
진행 중 데스크톱 마이크 상태와 모바일 컨트롤바에 지속 노출하고 접근성 role="note" 계약과 E2E를 고정했다.
Higgs·sample provider는 계속 dev-only이며 실존 인물/성우 reference를 사용하지 않는다. 새 공개 배포의
/voice/health에서 openai / gpt-4o-mini-tts exact match를 확인했다. authenticated WSS ready 확인은
공개 인증 상태와 소유 회기가 준비된 뒤 별도 외부 게이트로 수행한다.
후속 회귀는 Python 3.11로 voice service/WS/G7 store·API 73 passed, public runner 12 passed를 확인했다.
--confirm-physical-capture가 없는 full soak는 장치 열거 전 exit 3 BLOCKED로 남는다.
안전장치 추가 전 G533 마이크가 명시적 사용자 동의 없이 3초씩 두 번 열렸다. 약 -90 dBFS 무음이었고
원음·축어록을 보존하지 않았으며 즉시 추가 캡처를 중단했다. 이 실행은 G7 증거로 인정하지 않는다.
상세는 evidence/g7-external-voice-gate-audit-2026-08-07.json에 남겼다.