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

@ -8,6 +8,9 @@
[string]$CloudflaredConfig = "$env:USERPROFILE\.cloudflared\vignette-config.yml",
[string]$PublicHealthUrl = "https://api-vignette.chanpaca.net/health",
[string[]]$AdditionalPublicHealthUrls = @(),
# 게이트웨이가 shared secret으로 떠 있으면 /ready는 인증이 필요하다(/health만 면제).
# 토큰이 없으면 401을 장애로 오판해 재시작 폭풍이 난다.
[string]$EngineToken = $env:ENGINE_GATEWAY_SHARED_SECRET,
[string]$LogPath = "",
[int]$FailuresBeforeRestart = 3,
[switch]$CheckOnly,
@ -51,11 +54,16 @@ function Test-JsonHealth {
[string]$Name,
[string]$Uri,
[scriptblock]$IsHealthy,
[int]$TimeoutSec = 30
[int]$TimeoutSec = 30,
[hashtable]$Headers = $null
)
try {
$response = Invoke-RestMethod -Uri $Uri -TimeoutSec $TimeoutSec
if ($Headers) {
$response = Invoke-RestMethod -Uri $Uri -TimeoutSec $TimeoutSec -Headers $Headers
} else {
$response = Invoke-RestMethod -Uri $Uri -TimeoutSec $TimeoutSec
}
$ok = [bool](& $IsHealthy $response)
$detail = $response | ConvertTo-Json -Compress -Depth 5
[pscustomobject]@{
@ -64,14 +72,39 @@ function Test-JsonHealth {
Detail = $detail
}
} catch {
# 게이트웨이 /ready는 실패를 503 + JSON 본문으로 알린다. 본문을 버리면 로그에
# "(503)"만 남아 원인(만료 인증·플래그 오류)을 잃는다.
$detail = $_.Exception.Message
$body = ""
if ($_.ErrorDetails -and $_.ErrorDetails.Message) {
$body = $_.ErrorDetails.Message
} elseif ($_.Exception.Response) {
try {
$reader = New-Object System.IO.StreamReader($_.Exception.Response.GetResponseStream())
$body = $reader.ReadToEnd()
$reader.Dispose()
} catch {
$body = ""
}
}
if ($body) {
$detail = "$detail :: $body"
}
[pscustomobject]@{
Name = $Name
Ok = $false
Detail = $_.Exception.Message
Detail = $detail
}
}
}
function Get-EngineHeaders {
if ($EngineToken) {
return @{ "X-Vignette-Engine-Token" = $EngineToken }
}
return $null
}
function Test-CloudflaredProcess {
if ($SkipCloudflaredRestart) {
return [pscustomobject]@{
@ -102,15 +135,23 @@ if (!(Test-Path $startScript)) {
throw "Start script not found at $startScript"
}
# engine 판정은 /health(프로세스 liveness)가 아니라 /ready(실제 claude -p 생성)로 한다.
# /health는 ok:true만 보므로 "프로세스는 살아 있고 그 프로세스의 claude 세션만 죽은"
# 상태를 통과시킨다(2026-08-07 공개 런타임: engine=false인데 워치독 lastResult=0).
# /ready는 게이트웨이 readiness 캐시(ENGINE_READY_TTL_SECONDS)를 그대로 쓰므로
# 매 주기 LLM 호출로 이어지지 않는다. 콜드 스폰 여유로 타임아웃만 넉넉히 준다.
$checks = @(
(Test-JsonHealth `
-Name "engine" `
-Uri "http://127.0.0.1:$EnginePort/health" `
-IsHealthy { param($health) $health.ok -eq $true }),
-Uri "http://127.0.0.1:$EnginePort/ready" `
-IsHealthy { param($health) $health.ok -eq $true } `
-TimeoutSec 45 `
-Headers (Get-EngineHeaders)),
(Test-JsonHealth `
-Name "api" `
-Uri "http://127.0.0.1:$ApiPort/health" `
-IsHealthy { param($health) $health.environment -eq "prod" -and $health.db }),
-IsHealthy { param($health) $health.environment -eq "prod" -and $health.db -and $health.engine } `
-TimeoutSec 60),
(Test-JsonHealth `
-Name "web-preview" `
-Uri "http://127.0.0.1:$WebPort/" `
@ -180,11 +221,12 @@ try {
throw
}
# 재시작 직후 첫 health는 readiness 콜드 스폰(10~20초)을 포함하므로 여유를 준다.
$apiAfter = Test-JsonHealth `
-Name "api" `
-Uri "http://127.0.0.1:$ApiPort/health" `
-IsHealthy { param($health) $health.environment -eq "prod" -and $health.db } `
-TimeoutSec 20
-IsHealthy { param($health) $health.environment -eq "prod" -and $health.db -and $health.engine } `
-TimeoutSec 90
if (!$apiAfter.Ok) {
throw "Public API still unhealthy after restart: $($apiAfter.Detail)"
}
@ -200,9 +242,10 @@ if (!$webAfter.Ok) {
$engineAfter = Test-JsonHealth `
-Name "engine" `
-Uri "http://127.0.0.1:$EnginePort/health" `
-Uri "http://127.0.0.1:$EnginePort/ready" `
-IsHealthy { param($health) $health.ok -eq $true } `
-TimeoutSec 20
-TimeoutSec 60 `
-Headers (Get-EngineHeaders)
if (!$engineAfter.Ok) {
throw "Engine gateway still unhealthy after isolated restart: $($engineAfter.Detail)"
}