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 산출물은 커밋에서 제외했다.
This commit is contained in:
Yun Chan 2026-08-08 01:30:53 +09:00
parent 93dd8f82d7
commit 16e791e044
390 changed files with 243188 additions and 499 deletions

View file

@ -0,0 +1,554 @@
#!/usr/bin/env python3
"""Fail-closed validator for the complete G7 external evidence gate.
All four artifacts are required: authenticated public physical-microphone soak,
process-local voice runtime sampling, pinned Linux topology sampling, and an
independent human-held-out voice-gain pack. No artifact may contain raw audio or
transcripts, and synthetic evidence cannot satisfy this gate.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import math
import sys
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
REPO_ROOT = Path(__file__).resolve().parents[1]
API_ROOT = REPO_ROOT / "apps/api"
if str(API_ROOT) not in sys.path:
sys.path.insert(0, str(API_ROOT))
from app.contracts.g7_external_evidence import G7HumanVoiceGainEvidencePack # noqa: E402
from app.services.g7_voice_gain_evidence import evaluate_human_voice_gain # noqa: E402
MINIMUM_SOAK_SECONDS = 3_000.0
# 이 게이트는 특정 벤더가 아니라 **운영하기로 결정한 provider** 를 강제한다.
# 2026-08-08 소유자 결정: STT 는 노트북 상주 faster-whisper(`local_whisper`),
# TTS 는 노트북 Higgs(`higgs`). 이전 값은 벤더 하나(`deepgram`/`openai`)가
# 하드코딩돼 있었을 뿐 결정 기록이 아니었다.
#
# 목록은 닫혀 있다. 배치 STT(`openai`)는 interim/final 계약을 만족할 수 없어
# 여기 들어오지 못한다. 어떤 경우에도 `expected_* == ready_*` 결속은 유지되므로
# 선언한 provider 와 실제로 돈 provider 가 다르면 계속 실패한다.
ALLOWED_STT_PROVIDERS = ("local_whisper", "deepgram")
ALLOWED_TTS_PROVIDERS = ("higgs", "openai")
def load_json(path: Path, errors: list[str], label: str) -> dict[str, Any]:
try:
payload = json.loads(path.read_text(encoding="utf-8"))
except FileNotFoundError:
errors.append(f"{label}:missing")
return {}
except (OSError, UnicodeError, json.JSONDecodeError):
errors.append(f"{label}:unreadable")
return {}
if not isinstance(payload, dict):
errors.append(f"{label}:invalid_shape")
return {}
return payload
def _number(value: object) -> float | None:
if isinstance(value, bool) or not isinstance(value, (int, float)):
return None
result = float(value)
return result if math.isfinite(result) else None
def _require(errors: list[str], condition: bool, code: str) -> None:
if not condition:
errors.append(code)
def _iso(value: object) -> datetime | None:
if not isinstance(value, str) or not value:
return None
try:
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
except ValueError:
return None
if parsed.tzinfo is None:
parsed = parsed.replace(tzinfo=UTC)
return parsed.astimezone(UTC)
def validate_public_soak(payload: dict[str, Any], errors: list[str]) -> None:
prefix = "voice_soak"
_require(
errors,
payload.get("schema_version") == "vignette.g7-public-voice-soak.v4",
f"{prefix}:schema",
)
_require(errors, payload.get("mode") == "public_soak", f"{prefix}:mode")
_require(errors, payload.get("status") == "passed", f"{prefix}:status")
requested = _number(payload.get("requested_duration_seconds"))
elapsed = _number(payload.get("elapsed_seconds"))
_require(
errors,
requested is not None and requested >= MINIMUM_SOAK_SECONDS,
f"{prefix}:duration_requested",
)
_require(
errors,
elapsed is not None and requested is not None and elapsed >= requested,
f"{prefix}:duration_elapsed",
)
for field in (
"microphone_device_enumerated",
"physical_capture_confirmed",
"physical_microphone_used",
"public_wss_used",
"authenticated_public_wss_ready",
"real_database_session_binding_required",
"ready_provider_metadata_validated",
"cookie_present",
"session_id_present",
"cloudflare_ray_present",
"unauthenticated_handshake_accepted",
):
_require(errors, payload.get(field) is True, f"{prefix}:{field}")
for field in (
"raw_audio_retained",
"provider_overrides_used",
"cookie_value_logged",
):
_require(errors, payload.get(field) is False, f"{prefix}:{field}")
_require(errors, payload.get("tls_version") == "TLSv1.3", f"{prefix}:tls")
_require(
errors,
payload.get("unauthenticated_close_code") == 1008,
f"{prefix}:unauth_close",
)
_require(errors, payload.get("close_code") == 1000, f"{prefix}:auth_close")
_require(
errors,
payload.get("expected_stt_provider") in ALLOWED_STT_PROVIDERS,
f"{prefix}:stt_provider_not_operated",
)
_require(
errors,
payload.get("expected_tts_provider") in ALLOWED_TTS_PROVIDERS,
f"{prefix}:tts_provider_not_operated",
)
for expected, ready in (
("expected_stt_provider", "ready_stt_provider"),
("expected_stt_model", "ready_stt_model"),
("expected_tts_provider", "ready_tts_provider"),
("expected_tts_model", "ready_tts_model"),
):
_require(
errors,
bool(payload.get(expected)) and payload.get(expected) == payload.get(ready),
f"{prefix}:{ready}_mismatch",
)
attempted = payload.get("turns_attempted")
succeeded = payload.get("turns_succeeded")
metrics = payload.get("turn_transcript_metrics")
_require(
errors,
isinstance(attempted, int) and attempted >= 10,
f"{prefix}:turns_attempted",
)
_require(errors, succeeded == attempted, f"{prefix}:turns_succeeded")
_require(
errors,
isinstance(metrics, list) and len(metrics) == succeeded,
f"{prefix}:turn_metrics_count",
)
if isinstance(metrics, list):
interim_total = 0
final_total = 0
seen_turns: set[int] = set()
for item in metrics:
if not isinstance(item, dict):
errors.append(f"{prefix}:turn_metric_shape")
continue
turn = item.get("turn_number")
interim = item.get("interim_transcript_frames")
final = item.get("speech_final_transcript_frames")
_require(
errors,
isinstance(turn, int) and turn > 0 and turn not in seen_turns,
f"{prefix}:turn_number",
)
if isinstance(turn, int):
seen_turns.add(turn)
_require(
errors,
isinstance(interim, int) and interim >= 1,
f"{prefix}:interim_missing",
)
_require(errors, final == 1, f"{prefix}:speech_final_not_exact")
_require(
errors,
_number(item.get("first_interim_latency_ms")) is not None,
f"{prefix}:interim_latency",
)
_require(
errors,
_number(item.get("speech_final_latency_ms")) is not None,
f"{prefix}:final_latency",
)
interim_total += interim if isinstance(interim, int) else 0
final_total += final if isinstance(final, int) else 0
_require(
errors,
payload.get("interim_transcript_frames") == interim_total,
f"{prefix}:interim_total",
)
_require(
errors,
payload.get("speech_final_transcript_frames") == final_total,
f"{prefix}:final_total",
)
_require(
errors,
isinstance(succeeded, int) and payload.get("reply_frames", 0) >= succeeded,
f"{prefix}:reply_frames",
)
_require(
errors,
isinstance(succeeded, int) and payload.get("tts_end_frames", 0) >= succeeded,
f"{prefix}:tts_end_frames",
)
_require(errors, payload.get("tts_binary_bytes", 0) > 0, f"{prefix}:tts_audio")
_require(errors, payload.get("captured_pcm_bytes", 0) > 0, f"{prefix}:captured_pcm")
_require(errors, not payload.get("blockers"), f"{prefix}:blockers")
_require(errors, payload.get("failure_type") is None, f"{prefix}:failure")
def validate_runtime(payload: dict[str, Any], errors: list[str]) -> None:
prefix = "runtime"
_require(
errors,
payload.get("schema_version") == "vignette.g7-runtime-sampling.v1",
f"{prefix}:schema",
)
_require(errors, payload.get("status") == "passed", f"{prefix}:status")
_require(
errors,
payload.get("privacy_boundary")
== "metadata_only_no_audio_transcript_session_or_cookie_values",
f"{prefix}:privacy",
)
_require(
errors,
payload.get("cookie_present") is True
and payload.get("cookie_value_logged") is False,
f"{prefix}:cookie_boundary",
)
requested = payload.get("requested_samples")
completed = payload.get("samples_completed")
interval = _number(payload.get("interval_seconds"))
_require(
errors,
isinstance(requested, int) and requested == completed and requested > 1,
f"{prefix}:sample_count",
)
_require(
errors,
interval is not None
and isinstance(completed, int)
and (completed - 1) * interval >= MINIMUM_SOAK_SECONDS,
f"{prefix}:coverage",
)
samples = payload.get("samples")
_require(
errors,
isinstance(samples, list) and len(samples) == completed,
f"{prefix}:samples",
)
high = payload.get("high_water")
_require(errors, isinstance(high, dict), f"{prefix}:high_water")
if isinstance(high, dict):
for field in (
"process_peak_rss_bytes",
"process_threads_max",
"websocket_high_water",
"streaming_provider_session_high_water",
"route_audio_buffer_high_water_bytes",
"streaming_event_queue_high_water_items",
):
_require(errors, (_number(high.get(field)) or 0) > 0, f"{prefix}:{field}")
capture_started = _iso(payload.get("started_at_utc"))
worker_started = _iso(payload.get("worker_started_at_utc"))
_require(
errors,
capture_started is not None
and worker_started is not None
and 0 <= (capture_started - worker_started).total_seconds() <= 120,
f"{prefix}:fresh_worker_required",
)
if isinstance(samples, list) and samples:
final = samples[-1].get("snapshot") if isinstance(samples[-1], dict) else None
counters = final.get("counters") if isinstance(final, dict) else None
limits = final.get("limits") if isinstance(final, dict) else None
_require(
errors,
isinstance(final, dict)
and final.get("schema_version") == "vignette.voice-runtime.v1",
f"{prefix}:snapshot_schema",
)
_require(
errors,
isinstance(final, dict) and final.get("scope") == "single_api_worker",
f"{prefix}:snapshot_scope",
)
_require(
errors,
isinstance(final, dict)
and final.get("privacy_boundary")
== "metadata_only_no_audio_transcript_or_session_ids",
f"{prefix}:snapshot_privacy",
)
_require(errors, isinstance(counters, dict), f"{prefix}:final_counters")
_require(
errors,
isinstance(limits, dict) and limits.get("uvicorn_ws_max_queue") == 4,
f"{prefix}:uvicorn_queue_cap",
)
if isinstance(counters, dict):
for field in (
"provider_fallback_total",
"websocket_error_total",
"audio_overflow_rejections_total",
):
_require(errors, counters.get(field) == 0, f"{prefix}:{field}")
def validate_topology(payload: dict[str, Any], errors: list[str]) -> None:
prefix = "topology"
_require(
errors,
payload.get("schema_version") == "vignette.g7-topology-evidence.v1",
f"{prefix}:schema",
)
_require(errors, payload.get("status") == "passed", f"{prefix}:status")
scope = payload.get("scope")
_require(
errors,
isinstance(scope, dict) and scope.get("metadata_only") is True,
f"{prefix}:privacy",
)
if isinstance(scope, dict):
for field in (
"raw_command_output_retained",
"socket_endpoints_retained",
"request_payloads_retained",
"audio_retained",
"transcripts_retained",
):
_require(errors, scope.get(field) is False, f"{prefix}:{field}")
edge = scope.get("cloudflare_edge")
_require(
errors,
isinstance(edge, dict)
and edge.get("internal_queue_measured") is False
and edge.get("evidence_boundary") == "separate_external_artifact_required",
f"{prefix}:edge_boundary",
)
requested = payload.get("requested")
completed = payload.get("samples_completed")
_require(errors, isinstance(requested, dict), f"{prefix}:requested")
if isinstance(requested, dict):
sample_count = requested.get("samples")
interval = _number(requested.get("interval_seconds"))
_require(
errors,
isinstance(sample_count, int)
and sample_count == completed
and sample_count > 1,
f"{prefix}:sample_count",
)
_require(
errors,
interval is not None
and isinstance(completed, int)
and (completed - 1) * interval >= MINIMUM_SOAK_SECONDS,
f"{prefix}:coverage",
)
targets = payload.get("targets")
_require(
errors,
isinstance(targets, dict) and set(targets) == {"api", "caddy"},
f"{prefix}:targets",
)
if isinstance(targets, dict):
capture_started = _iso(payload.get("started_at_utc"))
for role in ("api", "caddy"):
target = targets.get(role)
_require(errors, isinstance(target, dict), f"{prefix}:{role}_target")
if isinstance(target, dict):
_require(
errors,
isinstance(target.get("container_id"), str)
and len(target["container_id"]) == 64,
f"{prefix}:{role}_container",
)
digest = target.get("image_digest")
_require(
errors,
isinstance(digest, str)
and digest.startswith("sha256:")
and len(digest) == 71,
f"{prefix}:{role}_image",
)
target_started = _iso(target.get("started_at"))
_require(
errors,
capture_started is not None
and target_started is not None
and 0 <= (capture_started - target_started).total_seconds() <= 120,
f"{prefix}:{role}_fresh_container_required",
)
summary = payload.get("summary")
_require(errors, isinstance(summary, dict), f"{prefix}:summary")
if isinstance(summary, dict):
containers = summary.get("containers")
host_tcp = summary.get("host_tcp")
_require(errors, isinstance(containers, dict), f"{prefix}:container_summary")
if isinstance(containers, dict):
for role in ("api", "caddy"):
values = containers.get(role)
_require(errors, isinstance(values, dict), f"{prefix}:{role}_summary")
if isinstance(values, dict):
for field in (
"cgroup_memory_peak_bytes_max",
"cgroup_cpu_usage_usec_max",
"cgroup_pids_current_max",
"proc_vm_hwm_bytes_max",
"proc_threads_max",
"proc_fd_count_max",
):
_require(
errors,
(_number(values.get(field)) or 0) > 0,
f"{prefix}:{role}_{field}",
)
_require(
errors,
isinstance(host_tcp, dict)
and (_number(host_tcp.get("connections_max")) or 0) > 0,
f"{prefix}:host_tcp",
)
_require(errors, payload.get("failure_type") is None, f"{prefix}:failure")
def validate_human_gain(payload: dict[str, Any], errors: list[str]) -> dict[str, Any]:
try:
pack = G7HumanVoiceGainEvidencePack.model_validate(payload)
result = evaluate_human_voice_gain(pack)
except Exception as exc:
errors.append(f"human_gain:invalid:{type(exc).__name__}")
return {}
if not result.passed:
errors.extend(f"human_gain:{reason}" for reason in result.failure_reasons)
return result.model_dump(mode="json")
def validate_binding(
voice: dict[str, Any],
runtime: dict[str, Any],
topology: dict[str, Any],
errors: list[str],
) -> None:
voice_host = voice.get("target_host")
runtime_host = runtime.get("target_host")
requested = topology.get("requested")
topology_host = (
requested.get("public_host") if isinstance(requested, dict) else None
)
_require(
errors,
bool(voice_host) and voice_host == runtime_host == topology_host,
"binding:public_host",
)
windows = []
for label, payload in (
("voice", voice),
("runtime", runtime),
("topology", topology),
):
start, end = (
_iso(payload.get("started_at_utc")),
_iso(payload.get("ended_at_utc")),
)
if start is None or end is None or end < start:
errors.append(f"binding:{label}_time")
else:
windows.append((start, end))
if len(windows) == 3:
_require(
errors,
max(start for start, _ in windows) <= min(end for _, end in windows),
"binding:no_concurrent_overlap",
)
def artifact_sha256(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def parser() -> argparse.ArgumentParser:
result = argparse.ArgumentParser(description=__doc__)
result.add_argument("--voice-soak", type=Path, required=True)
result.add_argument("--runtime", type=Path, required=True)
result.add_argument("--topology", type=Path, required=True)
result.add_argument("--human-voice-gain", type=Path, required=True)
result.add_argument("--json", action="store_true")
return result
def main() -> int:
args = parser().parse_args()
errors: list[str] = []
paths = {
"voice_soak": args.voice_soak,
"runtime": args.runtime,
"topology": args.topology,
"human_voice_gain": args.human_voice_gain,
}
payloads = {label: load_json(path, errors, label) for label, path in paths.items()}
if payloads["voice_soak"]:
validate_public_soak(payloads["voice_soak"], errors)
if payloads["runtime"]:
validate_runtime(payloads["runtime"], errors)
if payloads["topology"]:
validate_topology(payloads["topology"], errors)
gain_result = (
validate_human_gain(payloads["human_voice_gain"], errors)
if payloads["human_voice_gain"]
else {}
)
if all(payloads[label] for label in ("voice_soak", "runtime", "topology")):
validate_binding(
payloads["voice_soak"], payloads["runtime"], payloads["topology"], errors
)
report = {
"schema_version": "vignette.g7-external-proof-result.v1",
"passed": not errors,
"clinical_claim_allowed": False,
"artifact_sha256": {
label: artifact_sha256(path)
for label, path in paths.items()
if path.is_file()
},
"human_voice_gain_result": gain_result,
"errors": errors,
}
print(json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True))
return 0 if not errors else 1
if __name__ == "__main__":
raise SystemExit(main())