Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# orphan-kit-liveness-preflight — self-referential bootstrap

- `stack_kind=self_referential_bootstrap`
- Originating PR: `#653`
- Ledger entry: `orphan-kit-liveness-preflight`
- Opening base: `d72f3a66e738a8f1654707185cc14d836dd26759`
- Pre-opening branch head: `449e6eb0f3ca99dd8bcaad219ec08e3b68e53ece`
- Verification contract: `orphan-kit-liveness-preflight/v1`
- Contract SHA-256: `aaf83a0022155d2b8858e98570bbf15ead369b298ce8360402982a6e0c149223`
- Mechanism paths: `scripts/deploy.ps1`, `scripts/lib/host-native-launcher.ps1`, `scripts/self-referential-bootstrap-ledger.json`

本 PR 改的是 canonical deploy path 上「已經有實例在跑嗎、可以啟動嗎」這個判定
本身(issue #640)。兩層變更各自只在真實部署當下才存在:

1. `Start-HostNativeService` 新寫的 `<Name>.ports` claim,只有一次真的
`Start-Process` 啟動才會產生;`Remove-StalePidFile` 刻意不刪它,也只有在真實
部署把 launcher 弄死、Kit 子行程存活時才看得出差別。
2. `Get-HostNativeOrphanListener` 的拒絕,只有在真的有一個孤兒 Kit 佔著真的
LISTEN socket 時才會觸發;Phase 4c 的 exit 4 也只有在真實部署流程裡才會走到。

依 `docs/agents/self-referential-bootstrap.md` §2.1 第 2 類,部署契約只重建/驗證
已 merge 的 `origin/main` 內容(`scripts/deploy.ps1` 與
`scripts/lib/host-native-launcher.ps1` 皆列於 `Get-SelfReferentialMechanismPaths`),
因此在本變更抵達 origin/main 之前,無法用正規機制對「變更後行為」取證——不是
「報告格式沒有前版可比」那種 §2.1 明確排除的情況,而是契約本身禁止在 merge 前
用 canonical deployment 驗證變更後的啟動決策。同一形狀的既有實例為 PR #647
(`remote-deploy-tag-origin-main-sync`)。

單元測試能證明什麼、不能證明什麼,寫清楚:能證明的是 detection 與 refusal 的
邏輯——port claim 的寫入/保留/釋放、union 探測、live-tree 歸屬、
owner-not-visible(-1) fail-closed、settle window、以及 deploy.ps1 Phase 4c 在
`Start-HostNativeKit` 之前就評估這道閘門且以 exit 4 收尾。不能證明的是真實
canonical-linux 上「launcher 死掉、Kit 子行程仍活著佔埠」這個狀態下的端到端行為;
fixture 造出來的孤兒不等於一次真實部署週期。這正是本 debt 存在的理由,關帳留給
merge 後、以變更後機制重跑本 contract 五道命令的 ledger-only fixpoint PR。

本 evidence 只記錄 bootstrap opening,不是 canonical post-merge evidence,也不是
fixpoint evidence,不得用來關閉本 entry。

GitNexus:`gitnexus impact Get-HostNativeOrphanListener -d upstream -r
AI-BIM-governance` 回報 `Target not found`(`impactedCount=0`, `risk=UNKNOWN`)。
與 PR #647 記載的成因相同——GitNexus 目前不抽取 PowerShell 函式層級符號(`.ps1`
只以 File node 索引),屬工具涵蓋缺口而非索引過期,故記為 unavailable,不冒充
pass。替代證據為手動盤點的呼叫點:`Get-HostNativeOrphanListener` 只有兩個
consumer(`scripts/deploy.ps1` Phase 4c 與
`scripts/tests/test-host-native-launcher.ps1`);`<Name>.ports` sidecar 的
reader/writer 全數列舉為 `Start-HostNativeService`、`Stop-HostNativeService`、
`Get-HostNativeServiceListenPorts`、`Remove-StalePidFile`(刻意不動)。
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
stack_kind=self_referential_bootstrap
pr=653
ledger_entry=orphan-kit-liveness-preflight
opening_base=d72f3a66e738a8f1654707185cc14d836dd26759
pre_opening_head=449e6eb0f3ca99dd8bcaad219ec08e3b68e53ece
verification_contract=orphan-kit-liveness-preflight/v1
contract_sha256=aaf83a0022155d2b8858e98570bbf15ead369b298ce8360402982a6e0c149223
mechanism_paths=scripts/deploy.ps1,scripts/lib/host-native-launcher.ps1,scripts/self-referential-bootstrap-ledger.json

status=bootstrap_contract_pass
verified_at=2026-08-19T10:44:44Z
subject=working tree based on pre-opening head 449e6eb0f3ca99dd8bcaad219ec08e3b68e53ece with the exact ledger and evidence additions listed by this PR

test-host-native-launcher
command=pwsh -NoProfile -NonInteractive -File scripts/tests/test-host-native-launcher.ps1
result=exit 0; ALL PASSED, including the five new #640 cases (port claim recorded by a real launch; stale-pid cleanup keeps the claim; orphan detection matrix incl. dead recorded pid, live-tree attribution, unrelated holder, owner-not-visible sentinel, union probing and the settle window; only a stop that terminated something releases the claim; Kit launch declares its signal ports)

test-deploy-governance-static
command=pwsh -NoProfile -NonInteractive -File scripts/tests/test-deploy-governance-static.ps1
result=exit 0; PASS deploy governance static checks, including the new assertions that Phase 4c evaluates Get-HostNativeOrphanListener BEFORE Start-HostNativeKit, refuses with stage=4c, names scripts/stop-all.ps1, and exits through Print-FinalSummary -ExitCode 4

test-self-referential-bootstrap
command=pwsh -NoProfile -NonInteractive -File scripts/tests/test-self-referential-bootstrap.ps1
result=see PR body Validation section for the exact-head run

test-pr-body-evidence
command=pwsh -NoProfile -NonInteractive -File scripts/tests/test-pr-body-evidence.ps1
result=see PR body Validation section for the exact-head run

invoke-powershell-static
command=pwsh -NoProfile -NonInteractive -File scripts/tests/invoke-powershell-static.ps1
result=exit 0; [invoke-powershell-static] passed

gitnexus-impact
command=gitnexus impact Get-HostNativeOrphanListener -d upstream -r AI-BIM-governance
result=UNKNOWN/unavailable; PowerShell function-level symbols are not indexed by GitNexus (.ps1 files are File nodes only), the same coverage gap recorded for PR #647. Recorded as unavailable, not as a pass. Substitute evidence: manual call-site inventory - Get-HostNativeOrphanListener has exactly two consumers (scripts/deploy.ps1 Phase 4c, scripts/tests/test-host-native-launcher.ps1), and every reader/writer of the <Name>.ports sidecar is enumerated in the README next to this file.

windows-verification
tier=deploy_dryrun
result=scripts/deploy.ps1 -DryRun exits 0 on this Windows host; the exact-final-head GitHub Actions run URL is recorded in the PR body after the final push, not claimed by this pre-final-head bootstrap file. No live deployment, no service start, no port bind, and no Kit/GPU/WebRTC/browser evidence is claimed here.

No credential value was read or emitted. No approval, merge, runtime stop,
deployment mutation, or production action was performed while opening this
debt.
27 changes: 27 additions & 0 deletions scripts/deploy.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -1772,6 +1772,33 @@ if ($SkipKit) {
if ($kitAlreadyRunning) {
Write-DeployTag -Tag 'skip' -Message 'Phase 4c host-native Kit already running with matching runtime parameters' -LogPath $LogPath | Out-Null
} else {
# Orphan gate (#640). Liveness above is the LAUNCHER's pid, but the Kit
# child holds the ports, the GPU context and the Omniverse user
# directory. When the launcher dies and the child survives,
# Remove-StalePidFile correctly drops the pid file, Test-AlreadyRunning
# correctly reports "not running", and starting anyway put a second Kit
# on top of a live one: the new process deadlocked in early startup with
# two futex-waiting threads, no listener and not one line of its own log,
# and the deploy only found out 480s later.
#
# Phase 1 already prints this exact observation ("occupied by our PID
# ... already running, will skip start"). It was never a gate; this is.
# Refusing is the only honest option here: this run cannot tell a
# deadlocked orphan from a healthy instance, and it must not adopt one or
# race one. scripts/stop-all.ps1 stops by port as well as by pid file, so
# it reaches an orphan whose pid file is already gone - which is exactly
# the manual recovery that made the failing deployment pass unchanged.
$kitOrphan = Get-HostNativeOrphanListener `
-Name 'bim-streaming-server' `
-RunDir $RunDir `
-ExpectedPorts (@($resolvedKitSignalPort) + @($resolvedSpectatorSignalPorts))
if ($null -ne $kitOrphan) {
$orphanPortList = @($kitOrphan.Ports) -join ', '
$orphanPidList = @($kitOrphan.ProcessIds | ForEach-Object { if ([int]$_ -le 0) { 'owner-not-visible' } else { "$_" } }) -join ', '
Write-DeployTag -Tag 'fail' -Message "stage=4c Phase 4c refusing to start a second Kit: TCP port(s) $orphanPortList still LISTEN under PID(s) $orphanPidList, which no live bim-streaming-server PID file accounts for (orphaned Kit). Stop it first with scripts/stop-all.ps1, then re-run this deploy" -LogPath $LogPath | Out-Null
Print-FinalSummary -ExitCode 4 -FailedPhase 'Phase 4c (orphaned Kit holds the streaming ports)'
exit 4
}
Write-DeployTag -Tag 'ok' -Message 'Phase 4c starting host-native Kit streaming' -LogPath $LogPath | Out-Null
$startInfo = Start-HostNativeKit `
-RepoRoot $RepoRoot `
Expand Down
Loading
Loading