공개 런타임 롤백 경계 강화

This commit is contained in:
Yun Chan 2026-08-09 21:57:07 +09:00
parent ff3c79dfc2
commit aaebe4450e
7 changed files with 1271 additions and 30 deletions

View file

@ -16,6 +16,8 @@
[int]$ApiPort = 8001,
[int]$WebPort = 5174,
[int]$EnginePort = 9099,
[int]$WhisperPort = 9882,
[int]$MeloTtsPort = 9883,
[string]$Python = "$env:LOCALAPPDATA\Programs\Python\Python311\python.exe",
[string]$Cloudflared = "$env:LOCALAPPDATA\Microsoft\WinGet\Links\cloudflared.exe",
[string]$CloudflaredConfig = "$env:USERPROFILE\.cloudflared\vignette-config.yml",
@ -36,6 +38,7 @@ $ErrorActionPreference = "Stop"
$resolvedSourceRoot = (Resolve-Path -LiteralPath $StableSourceRoot).Path
$expectedWatchdogPath = Join-Path $resolvedSourceRoot "scripts\watch-public-runtime.ps1"
$startScript = Join-Path $resolvedSourceRoot "scripts\start-public-runtime.ps1"
$voiceSidecarProbe = Join-Path $resolvedSourceRoot "scripts\probe-public-voice-sidecars.py"
function Invoke-GitText {
param([string[]]$Arguments)
@ -54,6 +57,9 @@ function Assert-StableSourceProvenance {
if (-not (Test-Path -LiteralPath $startScript -PathType Leaf)) {
throw "Pinned start script not found at $startScript"
}
if (-not (Test-Path -LiteralPath $voiceSidecarProbe -PathType Leaf)) {
throw "Pinned voice sidecar probe not found at $voiceSidecarProbe"
}
$runningWatchdogPath = (Resolve-Path -LiteralPath $PSCommandPath).Path
if (-not [string]::Equals(
@ -99,7 +105,8 @@ function Assert-StableSourceProvenance {
foreach ($relativePath in @(
"scripts/watch-public-runtime.ps1",
"scripts/start-public-runtime.ps1"
"scripts/start-public-runtime.ps1",
"scripts/probe-public-voice-sidecars.py"
)) {
Invoke-GitText -Arguments @("ls-files", "--error-unmatch", "--", $relativePath) | Out-Null
}
@ -203,6 +210,47 @@ function Get-EngineHeaders {
return $null
}
function Test-VoiceApiReady {
param([object]$Health)
return (
$null -ne $Health -and
$Health.status -eq "ok" -and
$Health.available -eq $true -and
$Health.stt_available -eq $true -and
$Health.tts_available -eq $true -and
$Health.stt_provider -eq "local_whisper" -and
$Health.stt_model -eq "small" -and
$Health.tts_provider -eq "melotts" -and
$Health.tts_model -eq "melotts-korean" -and
$Health.limits.uvicorn_ws_max_queue -eq 4
)
}
function Test-VoiceSidecarStack {
$probeArgs = @(
"-X", "utf8", "-B", $voiceSidecarProbe,
"--component", "all",
"--stt-url", "ws://127.0.0.1:$WhisperPort/v1/listen",
"--stt-provider", "local_whisper",
"--stt-model", "small",
"--stt-language", "ko",
"--stt-device", "cpu",
"--tts-url", "http://127.0.0.1:$MeloTtsPort",
"--tts-provider", "melotts",
"--tts-model", "melotts-korean",
"--tts-language", "KR",
"--timeout-seconds", "5"
)
$output = @(& $Python @probeArgs 2>$null)
$probeExit = $LASTEXITCODE
return [pscustomobject]@{
Name = "voice-sidecars"
Ok = $probeExit -eq 0
Detail = if ($probeExit -eq 0) { (@($output) -join "") } else { "exact readiness probe failed" }
}
}
function Test-CloudflaredProcess {
if ($SkipCloudflaredRestart) {
return [pscustomobject]@{
@ -245,10 +293,16 @@ $checks = @(
-Uri "http://127.0.0.1:$ApiPort/health" `
-IsHealthy { param($health) $health.environment -eq "prod" -and $health.db -and $health.engine } `
-TimeoutSec 60),
(Test-JsonHealth `
-Name "voice-api" `
-Uri "http://127.0.0.1:$ApiPort/voice/health" `
-IsHealthy { param($health) Test-VoiceApiReady -Health $health } `
-TimeoutSec 30),
(Test-JsonHealth `
-Name "web-preview" `
-Uri "http://127.0.0.1:$WebPort/" `
-IsHealthy { param($body) $true }),
(Test-VoiceSidecarStack),
(Test-CloudflaredProcess)
)
@ -294,6 +348,8 @@ $startArgs = @{
ApiPort = $ApiPort
WebPort = $WebPort
EnginePort = $EnginePort
WhisperPort = $WhisperPort
MeloTtsPort = $MeloTtsPort
Python = $Python
Cloudflared = $Cloudflared
CloudflaredConfig = $CloudflaredConfig
@ -324,6 +380,15 @@ if (!$apiAfter.Ok) {
throw "Public API still unhealthy after restart: $($apiAfter.Detail)"
}
$voiceApiAfter = Test-JsonHealth `
-Name "voice-api" `
-Uri "http://127.0.0.1:$ApiPort/voice/health" `
-IsHealthy { param($health) Test-VoiceApiReady -Health $health } `
-TimeoutSec 30
if (!$voiceApiAfter.Ok) {
throw "Public voice API still does not expose the exact local provider/model contract after restart: $($voiceApiAfter.Detail)"
}
$webAfter = Test-JsonHealth `
-Name "web-preview" `
-Uri "http://127.0.0.1:$WebPort/" `
@ -343,6 +408,11 @@ if (!$engineAfter.Ok) {
throw "Engine gateway still unhealthy after isolated restart: $($engineAfter.Detail)"
}
$voiceSidecarsAfter = Test-VoiceSidecarStack
if (!$voiceSidecarsAfter.Ok) {
throw "Exact local voice sidecars still unhealthy after restart"
}
if (!$SkipPublicHealth) {
$publicAfter = Test-JsonHealth `
-Name "public-api" `