vignette/docs/ops/outcome-os-g7-external-proof-readiness-2026-08-07.md
2026-08-10 02:55:21 +09:00

12 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을 거부하고, held-out 공개 전에 끝난 사전등록·동의·participant split·model artifact·blind independent labeler provenance를 요구한다. production gate는 최소 31명(held-out 30명 외 calibration split 포함), 양 조건이 모두 관측된 완전 paired 50회기/150축, ICC(A,1) 0.75, 필수 categorical κ 0.70, gain 0.01, participant-cluster bootstrap 10,000회의 95% CI lower > 0을 재계산한다. 한 조건이라도 결측이면 양 조건을 모두 최대오류로 처리해 baseline-only 결측이 candidate gain을 부풀리지 못하게 한다.
  • 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, gateway 58 passed, API voice 통합 71 passed.
  • current G7 scripts 86/86, API human evaluator 11/11; Ruff·py_compile·git diff --check 통과.
  • Web typecheck와 API type contract 통과.
  • voice-success.spec.ts chromium single-run 2/2 통과. 동의 원장 응답을 의도적으로 지연한 동안 getUserMedia=0, voice WebSocket 0을 확인했고, 성공 뒤 각각 정확히 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

  1. soak-public-voice-websocket.py v4의 운영 기본값 local_whisper/melotts와 정확히 일치하는 ready metadata, authenticated public WSS· 명시 동의 물리 마이크 50분 양방향 passed evidence.
  2. 같은 시간창에 capture-g7-runtime-evidence.py로 얻은 관리자 endpoint worker/Uvicorn queue high-water evidence.
  3. 같은 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 증거를 별도 경계로 사용한다.
  4. 실제 동의 참가자와 독립 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는 production human pack을 마이크를 열기 전에 먼저 완전 검증하고, 통과한 경우에만 세 캡처를 동시에 시작한다. 종료 뒤 canonical checker가 같은 pack과 세 runtime artifact를 다시 검증한다. runtime/topology interval 기본값은 child collector 상한과 같은 60초이며, 60초 초과는 시작 전에 차단한다.

사람 데이터 운영자는 52분 실행과 독립적으로 pack을 먼저 검증할 수 있다. --print-schema는 authoritative JSON Schema를 출력하고, 오류는 JSON pointer와 유형만 반환해 participant/labeler key·라벨 값·PII를 반사하지 않는다.

복잡한 nested JSON을 사람이 직접 조립하지 않도록 intake 도구를 제공한다. --create-template는 header-only CSV와 template_only=true manifest를 만들어 증거로 사용할 수 없다. 실제 외부 protocol·consent·split·labeling·analysis artifact SHA와 비식별 participant/labeler/observation 수치를 채운 뒤 --compile하면 ICC·categorical κ와 production floor·gain·CI를 행에서 다시 계산한다. 모든 gate가 통과한 경우에만 기존 파일을 덮어쓰지 않고 최종 pack을 원자 생성한다. 이름·이메일·원음·축어록·자유서술 열은 받지 않으며 콘솔에도 입력 경로·키·라벨·잘못된 셀 값을 출력하지 않는다. 생성된 README.md는 사전등록 시각, key/split 규칙, status-score 조합, label taxonomy, production floor와 실제 compile/check 순서를 한 파일에서 설명하므로 독립 평가팀이 Python 소스를 읽지 않고도 입력을 준비할 수 있다.

& $py -X utf8 -B scripts/prepare-g7-human-voice-gain-intake.py --create-template <intake-dir>
# 실제 독립 평가팀이 manifest와 CSV를 채운 뒤에만 실행
& $py -X utf8 -B scripts/prepare-g7-human-voice-gain-intake.py `
  --compile <intake-dir> --out <pack.json>
& $py -X utf8 -B scripts/check-g7-human-voice-gain.py --print-schema
& $py -X utf8 -B scripts/check-g7-human-voice-gain.py --input <pack.json>
# 실제 실행 (물리 마이크 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
malformed·underpowered·κ 미달·불완전 paired human pack 물리 캡처 전에 human-pack 오류, exit 2
3,120초 미만 시간창 production_window_too_short, exit 2
runtime/topology interval 60초 초과 child collector 실행 전 interval 오류, 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-09 완료

공개 API/cloudflared는 detached-clean b34623f3…·tree 32cc85c7…에서 fresh 승격됐다. 공개 OpenAPI 126, /admin/voice-runtime, local_whisper/small, melotts/melotts-korean, Uvicorn WS queue 4와 receipt 9f8d1941…a21bf passed를 확인했다. watchdog·로그온 task도 같은 stable root에 source pin돼 명시 실행 결과 0이다.

30초 --rehearse는 authenticated WSS ready/ping/close1000, runtime 7 samples, Windows topology 7 samples를 모두 통과했다. evidence는 physical_capture=false이며 UUID/email literal 0이다. 이 리허설은 실제 물리 마이크와 human pack을 대체하지 않는다.

판정을 미인증 HTTP 상태로 하지 않는다. Cloudflare 앞단이 자동화 클라이언트에게 존재하지 않는 경로까지 포함해 모든 경로를 403(error code 1010) 으로 돌려주기 때문에, 상태 코드로는 "배포 누락"과 "edge 차단"을 구분할 수 없다. 처음엔 404/401 판정으로 짰다가 이 사실을 확인하고 OpenAPI spec의 paths만 authoritative하게 쓰도록 고쳤다. 회귀에 그 이유를 테스트로 남겼다.

따라서 남은 G7 실행 순서는 이렇다.

  1. 독립 연구팀이 실제 human pack을 작성하고 standalone validator를 통과시킨다.
  2. 사용자가 정확한 마이크 장치를 고르고 3,120초 물리 캡처에 명시 동의한다.
  3. production runner로 human pack을 재검증한 뒤 mic/runtime/topology 동시 창을 실행한다.
  4. canonical checker exit 0과 gate_closed=true를 확인한다.

기대 provider 기본값은 2026-08-08 결정에 맞춰 local_whisper/small / melotts/melotts-korean이다 (근거: ../decisions/local-voice-stack.md). current G7 scripts는 86/86, API human evaluator는 11/11이다. production exit 0은 checker_returncode == 0 && gate_closed is true에 결속되며 rehearse는 성공해도 gate를 닫지 않는다.