9.3 KiB
G7 external proof readiness — 2026-08-07
판정
G7은 내부 구현 DONE · external GATE다. 이번 변경은 물리 마이크나 운영 provider를 사용한 실증이 아니라, 외부 증거가 들어왔을 때 synthetic·preflight·짧은 probe로 잘못 닫히지 않도록 종료 게이트를 실행 가능한 네 artifact 계약으로 만든 것이다.
이번에 닫은 내부 범위
- Session의 첫 음성 사용은 인앱 설명과 명시 동의를 요구한다. 서버의 30일·원음 미보존
동의 원장 기록이 성공하기 전에는
getUserMedia와/voice/ws를 시작하지 않는다. 거절·원장 실패·지연 권한 응답·일시정지·회기 전환은 텍스트 대안을 유지하고, 뒤늦게 도착한 MediaStream의 track을 즉시 종료한다. 숨은 Space 단축키는 제거하고 안내 가능한Alt+M만 남겼다. - public WSS runner v4는 매 turn마다 interim 1개 이상과 speech-final 정확히 1개를 요구하고,
첫 interim·speech-final latency와 전체 합계를 metadata-only evidence에 기록한다. 장치 열거와
캡처는
--confirm-physical-capture없이는 시작하지 않는다. /admin/voice-runtime은 단일 API worker의 process RSS/peak RSS/CPU/thread/FD와 WebSocket, provider session, route audio buffer, streaming event queue, overflow/fallback/error high-water를 인증된 관리자에게만 반환한다. 원음·축어록·session ID·provider payload는 저장하지 않는다. production Docker Uvicorn WebSocket ingress queue는--ws-max-queue 4로 고정했다.- Linux topology sampler는 exact Compose project/service, container ID, image digest, init PID,
start/restart, cgroup path를 매 sample 전후 재검증한다. cgroup v2,
/proc, parsed Docker stats, endpoint를 폐기한 host TCP queue/retransmit의 50분 high-water를 만들 수 있다. - 누적 high-water를 이번 실행의 부하로 오인하지 않도록 Linux Compose API/container는 증거 시작
120초 이내의 동일 인스턴스여야 한다. Windows host mode는 detached-clean commit/tree, runner/collector/checker
SHA,
psutil==6.1.1, 새 API/cloudflared PID/start/exe·command SHA/실제 cwd를 매 sample 전후 검증한다. checker는 voice/runtime/topology 세 artifact의 공통 시간창이 3,000초 이상인지 재계산한다. - 독립 human-labeled held-out voice-gain 계약은 raw audio·transcript·synthetic pack을 거부하고,
사전등록·동의·participant split·model artifact·blind independent labeler provenance를 요구한다.
production gate는 30명/50회기/150 paired axis, ICC(A,1) 0.75, 선택적 κ 0.70, gain 0.01,
participant-cluster bootstrap 10,000회의 95% CI lower
> 0을 재계산한다. scripts/check-g7-external-proof.py는 같은 public host와 겹치는 시간창의 public soak, process-local runtime sampling, pinned topology sampling, independent human-gain pack 네 가지를 모두 통과시켜야 성공한다. G7 release gate는 이 checker 증거가 있을 때만satisfied-by-validated-g7-proof로 바뀔 수 있다.
검증
- API 전체
921 passed, gateway58 passed, API voice 통합71 passed. - runner/checker/topology
86 passed, public launcher/sidecar80 passed, G7 통합166 passed; Ruff·py_compile·PowerShell 5.1 parser·git diff --check통과. - Web typecheck와 API type contract 통과.
voice-success.spec.tschromium single-run2/2통과. 동의 원장 응답을 의도적으로 지연한 동안getUserMedia=0, voice WebSocket0을 확인했고, 성공 뒤 각각 정확히 1회였다.- 모든 브라우저 음성 테스트는 synthetic MediaStream과 controlled provider만 사용했다. 실제 장치 열거·캡처는 수행하지 않았다.
- 내부 hardening release SHA
a8c27b0aadcb9dacea86c4be5e62e19277b9cc2678ebca51aa02998d2c2d1a0d를 격리 NAS에 승격했다. 첫 clean-candidate 실행은 오래된 동의 fixture를 23/24에서 차단하고 원격을 변경하지 않았으며, 실제 동의 순서로 고친 뒤 24/24·OpenAPI 124·auth 401·G0~G8·NAS browser SSE→DB review를 통과했다. 이 증거에서도 물리 마이크는 사용하지 않았다.
실제 종료 때 필요한 네 artifact
soak-public-voice-websocket.pyv4의 운영 기본값local_whisper/melotts와 정확히 일치하는 ready metadata, authenticated public WSS· 명시 동의 물리 마이크 50분 양방향passedevidence.- 같은 시간창에
capture-g7-runtime-evidence.py로 얻은 관리자 endpoint worker/Uvicorn queue high-water evidence. - 같은 public host·exact images에
capture-g7-topology-evidence.py로 얻은 50분 cgroup/proc/Docker/TCP high-water evidence. Cloudflare 내부 큐는 직접 관측 가능하다고 주장하지 않고, public runner의 edge RTT/gap/disconnect/TLS/CF-Ray 증거를 별도 경계로 사용한다. - 실제 동의 참가자와 독립 blind labeler로 만든 human held-out voice-gain evidence pack.
네 파일을 check-g7-external-proof.py에 넣어 exit 0과 artifact SHA-256을 얻기 전에는 G7을
DONE으로 표시하지 않는다.
실행 오케스트레이터 (2026-08-08 추가)
앞의 1~3번은 같은 public transport host의 공통 3,000초 시간창이어야 하는데, 지금까지는 운영자가 세 명령을 따로
띄우고 시계를 손으로 맞춰야 했다. 50분짜리 실행에서 한 번 어긋나면 처음부터 다시 해야 한다.
scripts/run-g7-external-proof-window.py가 세 캡처를 동시에 시작하고, 전부 끝나면 human pack을
더해 checker까지 그대로 돌린다.
# 실제 실행 (물리 마이크 50분 + 사람 pack 필요)
& $py -X utf8 -B scripts/run-g7-external-proof-window.py `
--wss-url wss://api-vignette.chanpaca.net/voice/ws `
--origin https://vignette.chanpaca.net `
--admin-runtime-url https://api-vignette.chanpaca.net/admin/voice-runtime `
--topology-mode windows-host --repo-root <detached-clean-root> `
--git-sha <commit> --git-tree-sha <tree> `
--runner-script-sha256 <sha> --collector-script-sha256 <sha> `
--checker-script-sha256 <sha> --psutil-version 6.1.1 `
--api-pid <pid> --api-executable-name python.exe --api-executable-sha256 <sha> `
--api-cwd <root\apps\api> --api-listen-port 8001 `
--cloudflared-pid <pid> --cloudflared-executable-name cloudflared.exe `
--cloudflared-executable-sha256 <sha> --cloudflared-cwd <root> `
--microphone-device "<장치명>" --confirm-physical-capture `
--duration-seconds 3120 `
--human-voice-gain <pack.json> --out-dir <증거디렉터리>
게이트를 약화시키지 않는다. CLI로 실증한 주요 fail-closed 경계:
| 시도 | 결과 |
|---|---|
| 동의·장치 없이 운영 실행 | physical_microphone_consent_required, exit 2 |
| human pack 없이 운영 실행 | human_voice_gain_pack_required, exit 2 |
| 3,120초 미만 시간창 | production_window_too_short, exit 2 |
| transport host/scheme 불일치 | hosts_must_match 또는 scheme 오류, exit 2 |
| 허용되지 않은 browser Origin | query/원문을 반사하지 않는 Origin 오류, exit 2 |
--rehearse는 마이크·사람 없이 배관만 확인하는 모드다. soak을 --preflight-only로 돌려 장치를
열지 않고, 인자·경로·산출 파일까지 실제로 검증한다. 보고서의 gate_closed는 rehearse에서 항상
false다. 실제 50분 실행 전에 이걸로 먼저 실패를 뽑아내라.
⚠ 선행 조건 — 공개 런타임을 먼저 올려야 한다 (2026-08-08 발견)
마이크와 사람을 다 준비해도 오늘 실행하면 50분을 버리고 artifact 2에서 실패한다.
공개 런타임은 119 paths 구버전이고 /admin/voice-runtime이 배포돼 있지 않다.
capture-g7-runtime-evidence.py는 정확히 그 경로만 부르므로 artifact 2를 만들 수 없다.
현재 소스에는 있다(apps/api/app/routes/admin.py:1699, NAS 프리뷰 기준 126 paths).
공개 OpenAPI 119 paths · voice 경로 = /users/me/voice-presets, /voice/health, /voice/speech
has /admin/voice-runtime → False
그래서 오케스트레이터는 시작 전에 이걸 검사하고 admin_voice_runtime_not_deployed로 즉시 멈춘다.
실측: 공개 런타임 대상 --rehearse 실행이 exit 2로 몇 초 만에 차단됐다.
판정을 미인증 HTTP 상태로 하지 않는다. Cloudflare 앞단이 자동화 클라이언트에게 존재하지 않는
경로까지 포함해 모든 경로를 403(error code 1010) 으로 돌려주기 때문에, 상태 코드로는 "배포 누락"과
"edge 차단"을 구분할 수 없다. 처음엔 404/401 판정으로 짰다가 이 사실을 확인하고 OpenAPI spec의
paths만 authoritative하게 쓰도록 고쳤다. 회귀에 그 이유를 테스트로 남겼다.
따라서 G7 실행 순서는 이렇다.
- current source를 공개 런타임에 승격한다(비-secure origin
crypto.randomUUID수정도 여기 포함된다). --rehearse로 배관을 확인한다.- 동의 하 물리 마이크 50분 + human pack으로 실제 실행한다.
기대 provider 기본값은 2026-08-08 결정에 맞춰 local_whisper/small / melotts/melotts-korean이다
(근거: ../decisions/local-voice-stack.md). runner 회귀는 37/37, G7 통합은 166/166이다. production exit 0은
checker_returncode == 0 && gate_closed is true에 결속되며 rehearse는 성공해도 gate를 닫지 않는다.