161 lines
12 KiB
Markdown
161 lines
12 KiB
Markdown
# 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 소스를 읽지 않고도 입력을 준비할 수 있다.
|
|
|
|
```powershell
|
|
& $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>
|
|
```
|
|
|
|
```powershell
|
|
& $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>
|
|
```
|
|
|
|
```powershell
|
|
# 실제 실행 (물리 마이크 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를 닫지 않는다.
|