G7 증명과 G8 clean-head 승격 준비
This commit is contained in:
parent
94c681d450
commit
5221f79e3f
52 changed files with 6876 additions and 506 deletions
|
|
@ -1,5 +1,18 @@
|
|||
param(
|
||||
[string]$Workspace = "D:\workspace\vignette",
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$StableSourceRoot,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[ValidatePattern("^[0-9a-fA-F]{40}$")]
|
||||
[string]$ExpectedSourceCommit,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[ValidatePattern("^[0-9a-fA-F]{40}$")]
|
||||
[string]$ExpectedSourceTree,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[ValidatePattern("^[0-9a-fA-F]{64}$")]
|
||||
[string]$ExpectedWatchdogSha256,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[ValidatePattern("^[0-9a-fA-F]{64}$")]
|
||||
[string]$ExpectedStartScriptSha256,
|
||||
[int]$ApiPort = 8001,
|
||||
[int]$WebPort = 5174,
|
||||
[int]$EnginePort = 9099,
|
||||
|
|
@ -20,12 +33,97 @@
|
|||
|
||||
$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"
|
||||
|
||||
function Invoke-GitText {
|
||||
param([string[]]$Arguments)
|
||||
|
||||
$value = & git.exe -C $resolvedSourceRoot @Arguments
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Stable source Git command failed (exit=$LASTEXITCODE): git $($Arguments -join ' ')"
|
||||
}
|
||||
return (@($value) -join [Environment]::NewLine).Trim()
|
||||
}
|
||||
|
||||
function Assert-StableSourceProvenance {
|
||||
if (-not (Test-Path -LiteralPath $expectedWatchdogPath -PathType Leaf)) {
|
||||
throw "Pinned watchdog script not found at $expectedWatchdogPath"
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $startScript -PathType Leaf)) {
|
||||
throw "Pinned start script not found at $startScript"
|
||||
}
|
||||
|
||||
$runningWatchdogPath = (Resolve-Path -LiteralPath $PSCommandPath).Path
|
||||
if (-not [string]::Equals(
|
||||
$runningWatchdogPath,
|
||||
(Resolve-Path -LiteralPath $expectedWatchdogPath).Path,
|
||||
[System.StringComparison]::OrdinalIgnoreCase
|
||||
)) {
|
||||
throw "Watchdog is not executing from the pinned stable source root"
|
||||
}
|
||||
|
||||
$gitRoot = Invoke-GitText -Arguments @("rev-parse", "--show-toplevel")
|
||||
$resolvedGitRoot = (Resolve-Path -LiteralPath $gitRoot).Path
|
||||
if (-not [string]::Equals(
|
||||
$resolvedGitRoot,
|
||||
$resolvedSourceRoot,
|
||||
[System.StringComparison]::OrdinalIgnoreCase
|
||||
)) {
|
||||
throw "Stable source root does not match its Git toplevel"
|
||||
}
|
||||
|
||||
$symbolicHead = & git.exe -C $resolvedSourceRoot symbolic-ref --quiet HEAD
|
||||
$symbolicHeadExit = $LASTEXITCODE
|
||||
if ($symbolicHeadExit -eq 0) {
|
||||
throw "Stable source must be a detached HEAD, not branch $symbolicHead"
|
||||
}
|
||||
if ($symbolicHeadExit -ne 1) {
|
||||
throw "Could not prove detached HEAD (git exit=$symbolicHeadExit)"
|
||||
}
|
||||
|
||||
$actualCommit = Invoke-GitText -Arguments @("rev-parse", "--verify", "HEAD")
|
||||
$actualTree = Invoke-GitText -Arguments @("rev-parse", "--verify", "HEAD^{tree}")
|
||||
if ($actualCommit -ne $ExpectedSourceCommit.ToLowerInvariant()) {
|
||||
throw "Stable source commit drift: expected=$ExpectedSourceCommit actual=$actualCommit"
|
||||
}
|
||||
if ($actualTree -ne $ExpectedSourceTree.ToLowerInvariant()) {
|
||||
throw "Stable source tree drift: expected=$ExpectedSourceTree actual=$actualTree"
|
||||
}
|
||||
|
||||
$dirty = Invoke-GitText -Arguments @("status", "--porcelain=v1", "--untracked-files=normal")
|
||||
if ($dirty) {
|
||||
throw "Stable source is not clean; refusing runtime recovery"
|
||||
}
|
||||
|
||||
foreach ($relativePath in @(
|
||||
"scripts/watch-public-runtime.ps1",
|
||||
"scripts/start-public-runtime.ps1"
|
||||
)) {
|
||||
Invoke-GitText -Arguments @("ls-files", "--error-unmatch", "--", $relativePath) | Out-Null
|
||||
}
|
||||
|
||||
$actualWatchdogSha256 = (Get-FileHash -LiteralPath $expectedWatchdogPath -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
$actualStartScriptSha256 = (Get-FileHash -LiteralPath $startScript -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
if ($actualWatchdogSha256 -ne $ExpectedWatchdogSha256.ToLowerInvariant()) {
|
||||
throw "Pinned watchdog SHA256 drift"
|
||||
}
|
||||
if ($actualStartScriptSha256 -ne $ExpectedStartScriptSha256.ToLowerInvariant()) {
|
||||
throw "Pinned start script SHA256 drift"
|
||||
}
|
||||
}
|
||||
|
||||
# health probe, failcount 기록, 프로세스 재기동보다 먼저 source provenance를 닫는다.
|
||||
# 검증 실패는 운영 프로세스를 그대로 보존한 채 non-zero로 끝난다.
|
||||
Assert-StableSourceProvenance
|
||||
|
||||
if (!$LogPath) {
|
||||
$LogPath = Join-Path $Workspace "public-runtime-watchdog.log"
|
||||
$LogPath = Join-Path $resolvedSourceRoot "public-runtime-watchdog.log"
|
||||
}
|
||||
|
||||
# 연속 실패 카운터(재시작 debounce용). 워치독은 매 실행마다 새 프로세스라 파일로 유지한다.
|
||||
$FailCountPath = Join-Path $Workspace "public-runtime-watchdog.failcount"
|
||||
$FailCountPath = Join-Path $resolvedSourceRoot "public-runtime-watchdog.failcount"
|
||||
|
||||
function Get-FailCount {
|
||||
if (Test-Path $FailCountPath) {
|
||||
|
|
@ -130,11 +228,6 @@ function Test-CloudflaredProcess {
|
|||
}
|
||||
}
|
||||
|
||||
$startScript = Join-Path $PSScriptRoot "start-public-runtime.ps1"
|
||||
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).
|
||||
|
|
@ -197,7 +290,7 @@ if ($failCount -lt $FailuresBeforeRestart) {
|
|||
}
|
||||
|
||||
$startArgs = @{
|
||||
Workspace = $Workspace
|
||||
Workspace = $resolvedSourceRoot
|
||||
ApiPort = $ApiPort
|
||||
WebPort = $WebPort
|
||||
EnginePort = $EnginePort
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue