vignette/docs/ops/outcome-os-g7-wss-soak-evidence-2026-08-07.md
Yun Chan 16e791e044 G0~G8 성과·동맹 측정 OS 작업 일괄 고정
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 산출물은 커밋에서 제외했다.
2026-08-08 01:30:53 +09:00

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 soak 8.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했다.

  1. _principal_from_websocket: 승인된 synthetic learner 또는 인증 거부 상태
  2. _bind_session: synthetic 기존 회기 ID와 고정 voice preset
  3. multimodal_alliance_store.assert_voice_processing_allowed: 회기별 in-memory granted/withdrawn/미동의 gate
  4. voice_service.transcribe: 입력이 실제 WAV 정규화를 통과했는지 검사한 뒤 고정 transcript 반환
  5. _run_turn_and_speak: 고정 reply와 1개 synthetic binary TTS frame 반환
  6. voice_service.is_available: provider 정상/degraded 종료 분기를 결정적으로 전환
  7. _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/appapps/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_accuracyfused_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에 남겼다.