#!/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 deployment 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 ntpath 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 PINNED_PSUTIL_VERSION = "6.1.1" # 이 게이트는 특정 벤더가 아니라 **운영하기로 결정한 provider** 를 강제한다. # 2026-08-08 소유자 결정: STT 는 노트북 상주 faster-whisper(`local_whisper`), # TTS 는 노트북 MeloTTS(`melotts`). 둘 다 MIT 라 운영 사용 제약이 없다. # 이전 값은 벤더 하나(`deepgram`/`openai`)가 하드코딩돼 있었을 뿐 결정 기록이 아니었다. # 근거는 docs/decisions/local-voice-stack.md. # # 목록은 닫혀 있다. 배치 STT(`openai`)는 interim/final 계약을 만족할 수 없어 # 여기 들어오지 못한다. 어떤 경우에도 `expected_* == ready_*` 결속은 유지되므로 # 선언한 provider 와 실제로 돈 provider 가 다르면 계속 실패한다. ALLOWED_STT_PROVIDERS = ("local_whisper", "deepgram") ALLOWED_TTS_PROVIDERS = ("melotts", "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 _lower_hex(value: object, lengths: set[int]) -> bool: return ( isinstance(value, str) and len(value) in lengths and value == value.lower() and all(character in "0123456789abcdef" for character in value) ) 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_linux_compose_topology( payload: dict[str, Any], errors: list[str], ) -> None: prefix = "topology" 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", ) def _validate_windows_topology( payload: dict[str, Any], errors: list[str], ) -> None: prefix = "topology" requested = payload.get("requested") _require(errors, isinstance(requested, dict), f"{prefix}:windows_requested") if not isinstance(requested, dict): return expected_git_sha = requested.get("git_sha") _require( errors, _lower_hex(expected_git_sha, {40, 64}), f"{prefix}:windows_git_sha", ) source_pin = requested.get("source_pin") _require( errors, isinstance(source_pin, dict), f"{prefix}:windows_source_pin", ) expected_tree_sha = ( source_pin.get("git_tree_sha") if isinstance(source_pin, dict) else None ) expected_scripts = ( source_pin.get("script_sha256") if isinstance(source_pin, dict) else None ) expected_dependencies = ( source_pin.get("runtime_dependencies") if isinstance(source_pin, dict) else None ) _require( errors, _lower_hex(expected_tree_sha, {40, 64}), f"{prefix}:windows_git_tree_sha_pin", ) _require( errors, isinstance(expected_scripts, dict) and set(expected_scripts) == {"runner", "collector", "checker"} and all(_lower_hex(value, {64}) for value in expected_scripts.values()), f"{prefix}:windows_script_sha256_pin", ) _require( errors, isinstance(expected_dependencies, dict) and expected_dependencies == {"psutil": PINNED_PSUTIL_VERSION}, f"{prefix}:windows_psutil_version_pin", ) source_provenance = payload.get("source_provenance") _require( errors, isinstance(source_provenance, dict), f"{prefix}:windows_source_provenance", ) if isinstance(source_provenance, dict): observed_scripts = source_provenance.get("script_sha256") observed_dependencies = source_provenance.get("runtime_dependencies") _require( errors, source_provenance.get("detached_head") is True, f"{prefix}:windows_detached_head", ) _require( errors, source_provenance.get("tracked_clean") is True, f"{prefix}:windows_tracked_clean", ) _require( errors, source_provenance.get("git_sha") == expected_git_sha, f"{prefix}:windows_source_git_sha", ) _require( errors, source_provenance.get("git_tree_sha") == expected_tree_sha, f"{prefix}:windows_source_git_tree_sha", ) _require( errors, isinstance(observed_scripts, dict) and isinstance(expected_scripts, dict) and observed_scripts == expected_scripts, f"{prefix}:windows_source_script_sha256", ) _require( errors, isinstance(observed_dependencies, dict) and isinstance(expected_dependencies, dict) and observed_dependencies == expected_dependencies, f"{prefix}:windows_source_psutil_version", ) repo_root = requested.get("repo_root") _require( errors, isinstance(repo_root, str) and ntpath.isabs(repo_root), f"{prefix}:windows_repo_root", ) api_listen_port = requested.get("api_listen_port") _require( errors, isinstance(api_listen_port, int) and not isinstance(api_listen_port, bool) and 1 <= api_listen_port <= 65_535, f"{prefix}:windows_api_listen_port", ) requested_roles = requested.get("roles") _require( errors, isinstance(requested_roles, dict) and set(requested_roles) == {"api", "cloudflared"}, f"{prefix}:windows_requested_roles", ) targets = payload.get("targets") _require( errors, isinstance(targets, dict) and set(targets) == {"api", "cloudflared"}, f"{prefix}:targets", ) capture_started = _iso(payload.get("started_at_utc")) target_pids: list[int] = [] if isinstance(targets, dict) and isinstance(requested_roles, dict): for role in ("api", "cloudflared"): target = targets.get(role) expectation = requested_roles.get(role) _require(errors, isinstance(target, dict), f"{prefix}:{role}_target") _require( errors, isinstance(expectation, dict), f"{prefix}:{role}_expectation", ) if not isinstance(target, dict) or not isinstance(expectation, dict): continue _require( errors, target.get("role") == role, f"{prefix}:{role}_role", ) pid = target.get("pid") _require( errors, isinstance(pid, int) and not isinstance(pid, bool) and pid > 0, f"{prefix}:{role}_pid", ) if isinstance(pid, int) and not isinstance(pid, bool): target_pids.append(pid) _require( errors, pid == expectation.get("pid"), f"{prefix}:{role}_pid_pin", ) executable_sha256 = target.get("executable_sha256") _require( errors, _lower_hex(executable_sha256, {64}) and executable_sha256 == expectation.get("expected_executable_sha256"), f"{prefix}:{role}_executable_sha256", ) _require( errors, _lower_hex(target.get("command_line_sha256"), {64}), f"{prefix}:{role}_command_line_sha256", ) executable_name = target.get("executable_name") expected_name = expectation.get("expected_executable_name") _require( errors, isinstance(executable_name, str) and isinstance(expected_name, str) and executable_name.casefold() == expected_name.casefold(), f"{prefix}:{role}_executable_name", ) cwd = target.get("cwd") expected_cwd = expectation.get("expected_cwd") _require( errors, isinstance(cwd, str) and isinstance(expected_cwd, str) and ntpath.normcase(ntpath.normpath(cwd)) == ntpath.normcase(ntpath.normpath(expected_cwd)), f"{prefix}:{role}_cwd", ) _require( errors, target.get("git_sha") == expected_git_sha, f"{prefix}:{role}_git_sha", ) target_started = _iso(target.get("started_at")) _require( errors, capture_started is not None and target_started is not None and target_started <= capture_started, f"{prefix}:{role}_process_start", ) _require( errors, len(target_pids) == 2 and len(set(target_pids)) == 2, f"{prefix}:windows_distinct_pids", ) completed = payload.get("samples_completed") samples = payload.get("samples") _require( errors, isinstance(samples, list) and isinstance(completed, int) and len(samples) == completed, f"{prefix}:windows_samples", ) capture_ended = _iso(payload.get("ended_at_utc")) metric_values: dict[str, dict[str, list[float]]] = { role: { field: [] for field in ( "rss_bytes", "peak_rss_bytes", "cpu_time_seconds", "cpu_percent", "handles", "threads", ) } for role in ("api", "cloudflared") } tcp_values: dict[str, dict[str, list[int]]] = { role: {field: [] for field in ("connections", "established", "listeners")} for role in ("api", "cloudflared") } listener_owned: list[int] = [] listener_conflicts: list[int] = [] host_connections: list[int] = [] if isinstance(samples, list): previous_cpu: dict[str, float] = {} for index, sample in enumerate(samples, start=1): if not isinstance(sample, dict): errors.append(f"{prefix}:windows_sample_shape") continue _require( errors, sample.get("sequence") == index, f"{prefix}:windows_sample_sequence", ) observed_at = _iso(sample.get("observed_at_utc")) _require( errors, capture_started is not None and capture_ended is not None and observed_at is not None and capture_started <= observed_at <= capture_ended, f"{prefix}:windows_sample_time", ) processes = sample.get("processes") process_tcp = sample.get("process_tcp") _require( errors, isinstance(processes, dict) and set(processes) == {"api", "cloudflared"}, f"{prefix}:windows_sample_processes", ) _require( errors, isinstance(process_tcp, dict) and set(process_tcp) == {"api", "cloudflared"}, f"{prefix}:windows_sample_process_tcp", ) for role in ("api", "cloudflared"): metrics = processes.get(role) if isinstance(processes, dict) else None tcp = process_tcp.get(role) if isinstance(process_tcp, dict) else None _require( errors, isinstance(metrics, dict), f"{prefix}:{role}_sample_metrics", ) _require( errors, isinstance(tcp, dict), f"{prefix}:{role}_sample_tcp", ) if isinstance(metrics, dict): for field in metric_values[role]: numeric = _number(metrics.get(field)) positive = field != "cpu_percent" _require( errors, numeric is not None and numeric >= 0 and (not positive or numeric > 0), f"{prefix}:{role}_{field}_sample", ) if numeric is not None: metric_values[role][field].append(numeric) cpu_time = _number(metrics.get("cpu_time_seconds")) if cpu_time is not None: _require( errors, cpu_time >= previous_cpu.get(role, 0.0), f"{prefix}:{role}_cpu_time_monotonic", ) previous_cpu[role] = cpu_time if isinstance(tcp, dict): for field in tcp_values[role]: value = tcp.get(field) _require( errors, isinstance(value, int) and not isinstance(value, bool) and value >= 0, f"{prefix}:{role}_tcp_{field}_sample", ) if isinstance(value, int) and not isinstance(value, bool): tcp_values[role][field].append(value) if role == "api": _require( errors, (_number(tcp.get("listeners")) or 0) > 0, f"{prefix}:api_listener_sample", ) else: _require( errors, (_number(tcp.get("established")) or 0) > 0, f"{prefix}:cloudflared_tunnel_sample", ) listener = sample.get("api_listener") host_tcp = sample.get("host_tcp") _require(errors, isinstance(listener, dict), f"{prefix}:listener_sample") _require(errors, isinstance(host_tcp, dict), f"{prefix}:host_tcp_sample") if isinstance(listener, dict): owned = listener.get("owned_listener_count") conflicts = listener.get("conflicting_listener_count") _require( errors, listener.get("port") == api_listen_port, f"{prefix}:listener_port_sample", ) _require( errors, isinstance(owned, int) and not isinstance(owned, bool) and owned > 0, f"{prefix}:listener_owner_sample", ) _require( errors, isinstance(conflicts, int) and not isinstance(conflicts, bool) and conflicts == 0, f"{prefix}:listener_conflict_sample", ) if isinstance(owned, int) and not isinstance(owned, bool): listener_owned.append(owned) if isinstance(conflicts, int) and not isinstance(conflicts, bool): listener_conflicts.append(conflicts) if isinstance(host_tcp, dict): connections = host_tcp.get("connections") _require( errors, isinstance(connections, int) and not isinstance(connections, bool) and connections > 0, f"{prefix}:host_tcp_connections_sample", ) if isinstance(connections, int) and not isinstance(connections, bool): host_connections.append(connections) summary = payload.get("summary") _require(errors, isinstance(summary, dict), f"{prefix}:summary") if isinstance(summary, dict): process_summary = summary.get("processes") listener_summary = summary.get("api_listener") host_summary = summary.get("host_tcp") _require( errors, isinstance(process_summary, dict) and set(process_summary) == {"api", "cloudflared"}, f"{prefix}:windows_process_summary", ) if isinstance(process_summary, dict): for role in ("api", "cloudflared"): values = process_summary.get(role) _require( errors, isinstance(values, dict), f"{prefix}:{role}_summary", ) if not isinstance(values, dict): continue for field, observations in metric_values[role].items(): _require( errors, bool(observations) and _number(values.get(f"{field}_max")) == max(observations), f"{prefix}:{role}_{field}_max", ) for field, observations in tcp_values[role].items(): _require( errors, bool(observations) and _number(values.get(f"tcp_{field}_max")) == max(observations), f"{prefix}:{role}_tcp_{field}_max", ) _require( errors, isinstance(listener_summary, dict) and listener_summary.get("port") == api_listen_port and bool(listener_owned) and listener_summary.get("owned_listener_count_min") == min(listener_owned) and bool(listener_conflicts) and listener_summary.get("conflicting_listener_count_max") == max(listener_conflicts), f"{prefix}:windows_listener_summary", ) _require( errors, isinstance(host_summary, dict) and bool(host_connections) and host_summary.get("connections_max") == max(host_connections), f"{prefix}:host_tcp", ) 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", ) topology_mode = payload.get("topology_mode") _require( errors, topology_mode in (None, "linux_compose", "windows_host"), f"{prefix}:mode", ) if topology_mode == "windows_host": _require( errors, isinstance(scope, dict) and scope.get("topology_boundary") == "windows_host_api_and_cloudflared_processes" and scope.get("configured_listener_port_retained") is True, f"{prefix}:windows_scope", ) _validate_windows_topology(payload, errors) elif topology_mode in (None, "linux_compose"): _validate_linux_compose_topology(payload, errors) _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: overlap_started = max(start for start, _ in windows) overlap_ended = min(end for _, end in windows) _require( errors, overlap_started <= overlap_ended, "binding:no_concurrent_overlap", ) if overlap_started <= overlap_ended: _require( errors, (overlap_ended - overlap_started).total_seconds() >= MINIMUM_SOAK_SECONDS, "binding:concurrent_overlap_below_3000_seconds", ) 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())