Skip to content

fix(streaming): contain the Kit process tree on converter timeout (#489 PR-A) - #509

Merged
monkey1sai merged 22 commits into
mainfrom
fix/489a-converter-tree-containment
Aug 12, 2026
Merged

monkey1sai merged 22 commits into
mainfrom
fix/489a-converter-tree-containment

Conversation

@monkey1sai

@monkey1sai monkey1sai commented Aug 12, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • 修 [gate #484] SEC-001+L1-COR-004: outer timeout 未含 Kit process tree、terminator 只證 parent — 需 Job Object containment #489 的 PR-A 半部(高嚴重度、非 gate 檔):converter timeout 時整棵 Kit process tree 的 containment。
  • Python(SEC-001):_run_powershell_conversion 的 subprocess.run 換成 _ContainedProcess——Windows 用 Job Object(CreateJobObjectW+JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE+AssignProcessToJobObject,CREATE_SUSPENDED|CREATE_BREAKAWAY_FROM_JOB 啟動後 Toolhelp32 thread walk resume;ERROR_ACCESS_DENIED 時退回 suspended-only);POSIX 用 start_new_session+killpg SIGTERM→SIGKILL。timeout 時先殺樹、在 _hold_windows_hoops_entrypoint_for_execution pin 區塊內 poll 到所有 descendant 消失才 raise;無法證明清空時 raise 獨立 fail-closed 錯誤碼 converter_containment_failed(附殘存 PIDs),不偽裝成單純 timeout。tempfile 重導向保留(pipe 掛死不變式有專屬測試)。
  • PowerShell(L1-COR-004):convert-ifc-to-usdc.ps1 的 Stop-Process -Id 換成 Stop-ConverterProcessTree——BFS child walk(Win32_Process//proc stat)、葉先殺、等到每個收集的 PID 觀察消失,殘存者具名進 timeout 訊息。
  • 測試:monkeypatch converter 為「spawn 孫代+寫 PID 檔+sleep 超時」的真 fixture,斷言(a)正確錯誤碼(b)兩個 PID 都消失(c)bounded wall-clock。舊行為 probe:重放 subprocess.run(timeout=4) 對同 fixture 留下活孫代(PID 44240 存活實錄)——證明新測試是真回歸守門。
  • HARD SCOPE GUARD 遵守:未觸任何 self-referential gate 檔(bim-streaming-server/** 不在 mechanism regex;host-native-launcher 半部=[gate #484] SEC-001+L1-COR-004: outer timeout 未含 Kit process tree、terminator 只證 parent — 需 Job Object containment #489-B,另隨 mechanism bundle)。

AI Coding Governance

Machine values: Change lane = F / B / G / S; Behavior contract changed = yes / no; Requirement source = issue / docs/plans / superpowers spec / existing contract / not applicable.

Item Result
Change lane G
Behavior contract changed yes
Linked issue #489
Requirement source issue(#489,gate #484 findings SEC-001+L1-COR-004)
CODEOWNERS / owner review requested
GitNexus evidence not needed(index stale 且無這些 symbol;grep 全跡:_run_powershell_conversion 唯一 production caller=同檔 convert;Invoke-KitConversion 唯一 caller=同 ps1;新 symbol 無外部 caller)
Browser E2E evidence not user-facing
Agent workflow changed? no
Required checks expected CI / Agent Governance / PR Metadata Contract

Frontend Verification

User-facing changes must pass two independent producers: real frontend/runtime operability evidence and the pinned docs/plans/design-system-reference.manifest.json fidelity gate. Scope is derived from changed paths plus the base/head manifest union; the PR body cannot select an easier screen. mixed and partial_reference_missing permit honest partial work but require Full completion claimed = no. Semantic evidence is produced only by the design-semantic-visual CI Playwright job, never supplied as PR input; PR Metadata Contract validates the live PR metadata, while normal protected CI checks determine mergeability.

Item Result
Frontend route not applicable
Main button(s) tested not applicable
Fixture used not applicable
Backend API called not applicable
Runtime action not applicable
Visible success state not applicable
E2E command not applicable
Screenshot / trace not applicable
Design gate status not applicable
Design screen(s) not applicable
Reference-missing route(s) / surface(s) not applicable
Full completion claimed no
Design reference manifest docs/plans/design-system-reference.manifest.json
Visual fidelity result not applicable
Visual comparison not applicable
Visual artifacts not applicable
Manual test steps not applicable
Known gaps not applicable

Deploy Path Verification

Required for runtime / Docker / Kit / viewer / ports / env / conversion-service changes.

Item Result
Affects runtime / docker / Kit / viewer / ports / env? yes(conversion-service timeout 路徑;成功路徑行為不變)
Canonical deploy path updated? not needed(未觸 scripts/deploy.ps1;bim-streaming-server 內部)
New root script added? no
Deploy dry-run command not applicable(未觸 deploy path)
Full deploy tested not available(canonical rebuild 由 mechanism bundle 的 fixpoint 循環涵蓋)
Verify command .venv pytest bim-streaming-server/tests/test_host_native_conversion_service.py(104 passed/6 skipped)
Frontend URL verified not applicable
Evidence path PR body Windows On-Demand 節(真 IFC→USDC 轉檔實錄)

Windows On-Demand Verification

Required when changed paths can alter Windows platform behavior. The tier is machine-derived from changed paths (scripts/lib/windows-verification-scope.ps1); the highest match wins and the PR body cannot select an easier one. Docs and tests-only changes owe nothing.

Item Result
Windows verification tier kit_gpu
Windows verification evidence 於 reviewed head 841277a(kit_gpu tier 的真 GPU 轉檔實跑係於較早的 head 145381a 取得,本輪之後的 diff 未改轉檔語義,見本格 r4 段的範圍論證;CI run https://github.com/monkey1sai/AI-BIM-governance/actions/runs/31567124110;三輪 review+main 合流後最終 head)以本 branch 的 convert-ifc-to-usdc.ps1 對真實 89.4MB IFC(storage/demo_lib_2026.ifc)在 Windows host-native Kit(RTX 4060 Ti、deploy 區 kit.exe+omni.services.convert.cad-508.0.1)實跑:EXIT=0、39s、產出 28,462,810 bytes demo_lib_2026.usdc,與 origin/main baseline 腳本同輸入實跑(EXIT=0、35s)byte 數一致;另 pytest 127 passed/7 skipped(三輪 review 修復後 floor 104→127),含真 process-tree fixture 與 ps1 AST 抽出 Stop-ConverterProcessTree 對 live 2-deep tree 實測 survivors=[];r3 追加(head 145381ab2143e2549a0f6eb9d0f2a21192052972):本輪 diff 只動兩處——(a) convert-ifc-to-usdc.ps1 的平台偵測改走 Test-ConverterHostIsWindows(Get-Variable -ErrorAction SilentlyContinue 探測,取代會在 Windows PowerShell 5.1 +Set-StrictMode -Version Latest 下丟例外的裸 $IsWindows 解參考;Windows 分支的選擇結果不變),(b) POSIX 直接子行程的 reap 時序(os.waitid(..., WNOWAIT) 觀察退出、sweep 之後才 reap;Windows Job Object 路徑一行未動)。轉檔語義因此不變,上方真 IFC→USDC 實跑證據仍成立;於此 head 另在同一台 Windows host 實跑:pwsh scripts/deploy.ps1 -DryRun → EXIT=0(hybrid 模式、TCP/UDP 埠位全 FREE、storage root ALIGNED);pytest bim-streaming-server/tests/test_host_native_conversion_service.py -q → 130 passed/8 skipped(floor 127→130,新增 3 個 POSIX pgid 保留契約測試);pwsh bim-streaming-server/scripts/tests/test-convert-ifc-to-usdc.ps1 → passed(新增 Set-StrictMode -Version Latest 下的平台探測覆蓋,加上對整份腳本 AST 掃描、斷言不存在任何裸 $IsWindows 解參考的回歸守門);Invoke-ScriptAnalyzer -Severity Error 對兩支 ps1 皆 0 findings;python -m py_compile adapter+測試檔皆 OK。kit_gpu tier 的真 GPU 轉檔未於本輪重跑(此 worktree 無 kit.exe),誠實記錄為沿用前一 head 的實跑證據+上述 diff 範圍論證;r4 追加(head b483e87798903efbed52d0028e1d8b1e70fd0a87):本輪 diff 只動 convert-ifc-to-usdc.ps1 的 Stop-ConverterProcessTree 與其測試——多輪 discover→kill 迴圈在每次 Stop-Process 前重新驗證 (Id, StartTime) 行程身分,PID 已死或被回收即從後續回合、re-walk 種子與存活輪詢中剔除(修 P2:累積的 PID 集合每回合重殺,被回收的 PID 會讓 containment 殺到樹外的無關服務);身分在任一側讀不到時退回既有的存在性檢查,因此不會少殺原本殺得到的行程。於此 head 在同一台 Windows host 實跑:pwsh bim-streaming-server/scripts/tests/test-convert-ifc-to-usdc.ps1 → passed(新增 stale-PID 身分回歸案例,並以 mutation 反證:把重驗改回無條件 Stop-Process 即 fail);Invoke-ScriptAnalyzer -Severity Error 對兩支 ps1 皆 0 findings;pwsh scripts/deploy.ps1 -DryRun → EXIT=0(hybrid 模式、TCP/UDP 埠位全 FREE、storage root ALIGNED)。kit_gpu tier 的真 GPU 轉檔未於本輪重跑(此 worktree 無 kit.exe),轉檔成功路徑語義未變;r4 第二輪追加(head 841277a7c6e8f4319e4f32e764e5b2ab8b8654b0):reviewer 對上一個 commit 追加的第二個 P2——身分只在 kill 階段驗證仍嫌太晚,因為 re-walk 先跑:跨回合留存的 PID 若在本回合 walk 之前被回收,列舉它的子行程會把「無關行程的子代」以全新且自洽的身分收進 kill 集合,而反序 kill 會在辨識出被回收的 parent 之前先殺掉它們。修法:BFS 在展開任何 已知 PID 的子清單之前先重驗身分,recycled 直接跳過不展開;gone 仍照常展開(Windows 的 orphan 重新發現正是靠已死 parent 的 PID,case 2 回歸測試依賴此行為,且 gone 代表沒有任何行程持有該 PID,不可能經它觸及無關行程)。於此 head 實跑:pwsh bim-streaming-server/scripts/tests/test-convert-ifc-to-usdc.ps1 → passed(新增 case 5:以 shadow Stop-Process 記錄「被送出訊號的 PID」,斷言經由被回收 PID 觸及的無關子行程從未被列舉、未被送訊號、也未列入 survivors;mutation 反證:移除 walk 階段的重驗即 fail);Invoke-ScriptAnalyzer -Severity Error 兩支 ps1 皆 0 findings;pwsh scripts/deploy.ps1 -DryRun → EXIT=0

Self-Referential Bootstrap

Required when the PR changes the verification mechanism itself (deploy path / evidence harness / gate script). Rule: docs/agents/self-referential-bootstrap.md. Open ledger debt in scripts/self-referential-bootstrap-ledger.json blocks further mechanism PRs until fixpoint closure.

Item Result
Self-referential bootstrap no
Bootstrap ledger entry not applicable
Bootstrap reason not applicable

Validation

  • .venv\Scripts\python.exe -m pytest bim-streaming-server/tests/test_host_native_conversion_service.py -q → 104 passed, 6 skipped(baseline 101/6;3 輪重跑穩定)。test_converter_subprocess_uses_file_redirection_not_pipes 與 TimeoutExpired 測試保綠、原斷言全保留。
  • 全套 bim-streaming-server/tests → 7 failed/234 passed/6 skipped,7 紅全屬 pre-existing test_stage_loading_stage_composition.py(缺 pytest-asyncio,未動的 main 上同樣重現 7 failed)。
  • bim-streaming-server/scripts/tests/test-convert-ifc-to-usdc.ps1 → passed;PowerShell 5.1 AST parse OK。
  • 真轉檔 A/B:本 branch 腳本 EXIT=0/34s vs origin/main baseline EXIT=0/35s,同輸入同 bytes 輸出。

Known Risks

  • R3-2 行為變更(merge 前值得刻意一看):containment 證明現在跑於所有離開路徑(含成功),無法證明邊界時 returncode 0 會轉 converter_containment_failed——唯一可能讓既有成功轉檔轉紅的變更;已以真行程+真 Job Object 驗證成功路徑不受拖累。

  • 512-PID job 清單上限現為硬失敗邊界(正常個位數行程;踩到即異常訊號)。

  • POSIX 分支(killpg//proc walk)本機為 win32 未實跑,code-review only;canonical-linux 驗證段補真跑。

  • 新錯誤碼 converter_containment_failed 經 result.error.code 上浮;grep 全 repo 無列舉 converter codes 的 contract,下游若有 switch-on-code 消費者需留意。

  • Windows 若 real child 無法 resume(suspended),fail-closed 殺掉並回 converter_unavailable——本機實測 resume 正常,其他 Windows 組態未測。

  • 觀察備註:兩次在多 agent 高負載並行下的轉檔 wrapper 曾逾 10 分鐘未返回(被殺;USDC 已完整產出);單獨重跑(run3)與 baseline 皆 34–35s 乾淨退出,root cause 未定,先誠實記錄供觀察,不影響本 diff 的 timeout 語義(該路徑有 bounded-deadline 測試鎖定)。

🤖 Generated with Claude Code

#489 SEC-001: `_run_powershell_conversion` used `subprocess.run(timeout=...)`,
whose timeout only `kill()`s the direct PowerShell child. Kit grandchildren kept
processing untrusted input while the `with` block unwound and released the HOOPS
entrypoint pin.

- Replace `subprocess.run` with `_ContainedProcess`, which spawns the converter
  inside an OS-level containment boundary: on Windows a Job Object with
  JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE (launched CREATE_SUSPENDED |
  CREATE_BREAKAWAY_FROM_JOB, assigned, then resumed via a Toolhelp thread walk);
  on POSIX its own session/process group.
- On timeout the whole tree is terminated and then POLLED until every descendant
  is observed gone, and that poll runs INSIDE the
  `_hold_windows_hoops_entrypoint_for_execution` block so the pin outlives the
  tree. Only then may the function raise.
- When containment cannot be proven within the deadline the adapter fails closed
  with a distinct `converter_containment_failed` reason (with surviving PIDs)
  instead of a plain `converter_timeout`.
- Tempfile stdout/stderr redirection is preserved; pipes are still never used
  (the pipe-hang invariant test now asserts it at the `Popen` seam and checks the
  ps1-timeout + 30s buffer is still what the wait is given).

#489 L1-COR-004 (same-family adjacent hole): `convert-ifc-to-usdc.ps1` only did
`Stop-Process -Id $process.Id -Force` on its own `-TimeoutSeconds` expiry. It now
uses `Stop-ConverterProcessTree`, a breadth-first child walk (same idiom as
`Stop-HostNativeService`) that kills leaves first and then waits until every
collected PID is observed gone, reporting surviving PIDs in the timeout message.

Tests: new real-process test spawns a child + grandchild, records both PIDs and
sleeps past `timeout_seconds + buffer`; asserts `converter_timeout`, both PIDs
gone, and a bounded wall clock. Plus ordering test (pin acquired -> tree
contained -> pin released) and an unprovable-containment fail-closed test.

Verified: .venv pytest test_host_native_conversion_service.py 104 passed,
6 skipped (baseline 101 passed, 6 skipped); bim-streaming-server ps1 test suite
passed; PowerShell AST parse check passed; behavioural probe of
Stop-ConverterProcessTree terminated a real 2-deep tree with zero survivors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings August 12, 2026 02:59
@monkey1sai
monkey1sai enabled auto-merge (squash) August 12, 2026 02:59
@coderabbitai

coderabbitai Bot commented Aug 12, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: bac2b471-4cda-46bc-825d-bbbab62f7db1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The converter now runs in cross-platform containment boundaries. Timeout handling terminates and verifies the full process tree, preserves logs, keeps the HOOPS entrypoint pinned during cleanup, and reports distinct containment failures.

Changes

Converter process containment

Layer / File(s) Summary
PowerShell process-tree cleanup
bim-streaming-server/scripts/convert-ifc-to-usdc.ps1
The script discovers descendants on Windows and Linux, terminates the full tree, waits for exit, and reports surviving PIDs.
Contained adapter execution
bim-streaming-server/source/extensions/ezplus.bim_review_stream.messaging/ezplus/bim_review_stream/messaging/ifc2usdc_powershell_adapter.py
The adapter uses Windows Job Objects or POSIX process groups, captures logs, applies bounded cleanup, and distinguishes timeout and containment errors.
Containment behavior validation
bim-streaming-server/tests/test_host_native_conversion_service.py
Tests cover Popen execution, process-tree termination, timeout bounds, HOOPS pin ordering, and fail-closed containment errors.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ConversionService
  participant ContainedProcess
  participant Converter
  participant ProcessTree
  ConversionService->>ContainedProcess: launch converter
  ContainedProcess->>Converter: run within containment boundary
  ConversionService->>ContainedProcess: wait with timeout
  ContainedProcess->>ProcessTree: terminate descendants on timeout
  ProcessTree-->>ContainedProcess: return surviving PIDs
  ContainedProcess-->>ConversionService: return result or containment error
Loading

Possibly related PRs

Suggested reviewers: monkey1sai-blip

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the main change: containing the Kit process tree when the converter times out.
Description check ✅ Passed The description directly explains the process-containment changes, tests, validation, risks, and linked issue.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/489a-converter-tree-containment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
bim-streaming-server/scripts/convert-ifc-to-usdc.ps1 (1)

470-482: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Bound the post-timeout WaitForExit when containment fails.

Stop-ConverterProcessTree can return surviving PIDs, and the root Kit PID is part of the collected tree. If Kit survives, the finally block at line 486 calls $process.WaitForExit() with no timeout. The script then blocks forever and never reports the containment failure that lines 504-509 were added to surface. Pass a bounded timeout there, and keep the drain best-effort.

🛠️ Proposed fix (line 486, outside the selected range)
-        try { $process.WaitForExit() } catch { }
+        # Bounded: after a failed containment the child may still be alive, and an
+        # unbounded wait would hide the surviving-PID timeout report.
+        try { [void]$process.WaitForExit(5000) } catch { }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bim-streaming-server/scripts/convert-ifc-to-usdc.ps1` around lines 470 - 482,
Update the post-timeout drain in the finally block associated with
Stop-ConverterProcessTree to call $process.WaitForExit with a bounded timeout
instead of the parameterless overload. Keep this drain best-effort so execution
proceeds to the existing surviving-PID reporting and timeout handling when the
Kit process remains alive.
🧹 Nitpick comments (4)
bim-streaming-server/tests/test_host_native_conversion_service.py (1)

3616-3618: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Assert against the adapter constant, not the literal 30.

The literal duplicates _OUTER_TIMEOUT_BUFFER_SECONDS. If the buffer changes, this assertion fails for a reason that is unrelated to the redirection contract it verifies.

♻️ Proposed refactor
-    assert captured["proc"].wait_timeouts == [adapter.timeout_seconds + 30]
+    assert captured["proc"].wait_timeouts == [
+        adapter.timeout_seconds + adapter._OUTER_TIMEOUT_BUFFER_SECONDS
+    ]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bim-streaming-server/tests/test_host_native_conversion_service.py` around
lines 3616 - 3618, Update the wait-timeout assertion for captured["proc"] to use
the adapter’s _OUTER_TIMEOUT_BUFFER_SECONDS constant instead of the literal 30,
while preserving the expected adapter.timeout_seconds plus buffer contract.
bim-streaming-server/scripts/convert-ifc-to-usdc.ps1 (2)

335-360: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Re-walk the tree after termination to catch late descendants.

Stop-ConverterProcessTree enumerates the tree once, then terminates from leaf to root. The root Kit process stays alive until the last iteration, so it can spawn new children after enumeration. The poll loop only inspects $ids, so a late descendant is neither killed nor reported. Consider re-collecting children after the first kill pass and repeating termination until the walk returns no new PIDs.

♻️ Proposed refactor
-    for ($i = $ids.Count - 1; $i -ge 0; $i--) {
-        Stop-Process -Id ([int]$ids[$i]) -Force -ErrorAction SilentlyContinue
-    }
+    for ($pass = 0; $pass -lt 3; $pass++) {
+        for ($i = $ids.Count - 1; $i -ge 0; $i--) {
+            Stop-Process -Id ([int]$ids[$i]) -Force -ErrorAction SilentlyContinue
+        }
+        # Descendants spawned between enumeration and termination are only
+        # visible on a second walk.
+        $rescan = @()
+        foreach ($known in $ids) {
+            $rescan += @(Get-ConverterChildProcessId -ParentProcessId $known)
+        }
+        $fresh = @($rescan | Where-Object { $ids -notcontains $_ })
+        if ($fresh.Count -eq 0) { break }
+        $ids += $fresh
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bim-streaming-server/scripts/convert-ifc-to-usdc.ps1` around lines 335 - 360,
Update Stop-ConverterProcessTree to re-walk the process tree after each
termination pass, collecting any newly spawned descendant PIDs and terminating
them leaf-first. Repeat collection and termination until no new PIDs are found,
then retain the existing wait/reporting behavior so late descendants are also
killed or returned as survivors.

308-333: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Fail closed on unsupported process-enumeration platforms

Keep the check compatible with Windows PowerShell 5.1 (powershell.exe) instead of referencing $IsWindows directly. When /proc is unavailable, fail the containment check instead of treating an empty child list as confirmed termination.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bim-streaming-server/scripts/convert-ifc-to-usdc.ps1` around lines 308 - 333,
The Get-ConverterChildProcessId process-enumeration path must fail closed when
running on an unsupported platform without /proc, rather than returning an empty
list that implies successful containment. Keep platform detection compatible
with Windows PowerShell 5.1 by using the existing $env:OS check and add an
explicit unsupported-platform failure when /proc is unavailable, while
preserving the Windows CIM and Linux /proc behavior.
bim-streaming-server/source/extensions/ezplus.bim_review_stream.messaging/ezplus/bim_review_stream/messaging/ifc2usdc_powershell_adapter.py (1)

376-378: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Static analysis flags on _popen are false positives.

Ruff S603 and the ast-grep subprocess-from-request rule report untrusted command execution here. cmd originates from _build_converter_command, which assembles resolved repository paths and validated arguments, and shell=False is always set at the call sites. No request-controlled string reaches the argument vector. Consider a # noqa: S603 with a short justification so the finding does not recur on every scan.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@bim-streaming-server/source/extensions/ezplus.bim_review_stream.messaging/ezplus/bim_review_stream/messaging/ifc2usdc_powershell_adapter.py`
around lines 376 - 378, The static-analysis suppression belongs on the
subprocess invocation in _popen, not its callers. Add a narrowly scoped # noqa:
S603 with a brief justification that cmd is built from resolved repository paths
and validated arguments and that callers always use shell=False; preserve the
existing Popen behavior.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@bim-streaming-server/source/extensions/ezplus.bim_review_stream.messaging/ezplus/bim_review_stream/messaging/ifc2usdc_powershell_adapter.py`:
- Around line 465-473: Update the POSIX branch of close() to terminate the
converter’s process group when releasing containment, while preserving the
existing Windows job-handle cleanup and idempotent _closed guard. Ensure normal
as well as timeout exits cannot leave Kit grandchildren running.
- Around line 539-565: Update _terminate_tree_posix so deadline failure
populates self._survivors with the currently live PIDs in the process group,
discovered by enumerating /proc, rather than defaulting to the direct child pid.
Preserve the empty-survivor success paths and ensure
converter_containment_failed receives the actual surviving group members.

In `@bim-streaming-server/tests/test_host_native_conversion_service.py`:
- Around line 3704-3755: Add test execution for
test_host_native_conversion_service.py to the canonical Linux and Windows CI
test jobs, ensuring both workflows invoke this test file through their existing
test commands. Do not modify the test’s finally cleanup or process-tree
assertions.

---

Outside diff comments:
In `@bim-streaming-server/scripts/convert-ifc-to-usdc.ps1`:
- Around line 470-482: Update the post-timeout drain in the finally block
associated with Stop-ConverterProcessTree to call $process.WaitForExit with a
bounded timeout instead of the parameterless overload. Keep this drain
best-effort so execution proceeds to the existing surviving-PID reporting and
timeout handling when the Kit process remains alive.

---

Nitpick comments:
In `@bim-streaming-server/scripts/convert-ifc-to-usdc.ps1`:
- Around line 335-360: Update Stop-ConverterProcessTree to re-walk the process
tree after each termination pass, collecting any newly spawned descendant PIDs
and terminating them leaf-first. Repeat collection and termination until no new
PIDs are found, then retain the existing wait/reporting behavior so late
descendants are also killed or returned as survivors.
- Around line 308-333: The Get-ConverterChildProcessId process-enumeration path
must fail closed when running on an unsupported platform without /proc, rather
than returning an empty list that implies successful containment. Keep platform
detection compatible with Windows PowerShell 5.1 by using the existing $env:OS
check and add an explicit unsupported-platform failure when /proc is
unavailable, while preserving the Windows CIM and Linux /proc behavior.

In
`@bim-streaming-server/source/extensions/ezplus.bim_review_stream.messaging/ezplus/bim_review_stream/messaging/ifc2usdc_powershell_adapter.py`:
- Around line 376-378: The static-analysis suppression belongs on the subprocess
invocation in _popen, not its callers. Add a narrowly scoped # noqa: S603 with a
brief justification that cmd is built from resolved repository paths and
validated arguments and that callers always use shell=False; preserve the
existing Popen behavior.

In `@bim-streaming-server/tests/test_host_native_conversion_service.py`:
- Around line 3616-3618: Update the wait-timeout assertion for captured["proc"]
to use the adapter’s _OUTER_TIMEOUT_BUFFER_SECONDS constant instead of the
literal 30, while preserving the expected adapter.timeout_seconds plus buffer
contract.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: de1f838b-2e23-4a27-b27e-9caa2550e0dd

📥 Commits

Reviewing files that changed from the base of the PR and between 970dc34 and 19cec83.

📒 Files selected for processing (3)
  • bim-streaming-server/scripts/convert-ifc-to-usdc.ps1
  • bim-streaming-server/source/extensions/ezplus.bim_review_stream.messaging/ezplus/bim_review_stream/messaging/ifc2usdc_powershell_adapter.py
  • bim-streaming-server/tests/test_host_native_conversion_service.py

Comment thread bim-streaming-server/tests/test_host_native_conversion_service.py

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements the "PR-A" half of issue #489, hardening the IFC→USDC converter so that a timeout contains the entire Kit process tree, not just the direct PowerShell child. Previously, subprocess.run(timeout=...) only killed the immediate child, allowing Kit grandchildren to keep processing untrusted input while the HOOPS pin was released — a containment gap flagged as SEC-001 (high) and L1-COR-004 (medium).

Changes:

  • Python (SEC-001): Replaces subprocess.run in _run_powershell_conversion with a new _ContainedProcess abstraction — Windows Job Objects (JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, CREATE_SUSPENDED race protection, Toolhelp thread resume) and POSIX start_new_session + killpg. On timeout it kills the tree and polls until every descendant is observed gone before leaving the HOOPS pin block; when it cannot prove the tree is empty it raises a distinct fail-closed code converter_containment_failed (with surviving PIDs) rather than a plain converter_timeout.
  • PowerShell (L1-COR-004): Replaces Stop-Process -Id with Stop-ConverterProcessTree — a BFS child walk (Win32_Process / /proc), leaf-first kills, and a bounded wait until every collected PID is confirmed gone, naming survivors in the timeout message.
  • Tests: Adapts existing subprocess.run doubles to a subprocess.Popen seam (_FakeContainedPopen/_patch_popen) and adds real process-tree integration, pin-ordering, and fail-closed regression tests.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
bim-streaming-server/source/.../ifc2usdc_powershell_adapter.py Adds _Win32ContainmentApi + _ContainedProcess; refactors _run_powershell_conversion into _build_converter_command / _spawn_contained_converter with tree-containment and the new fail-closed error code.
bim-streaming-server/scripts/convert-ifc-to-usdc.ps1 Adds Get-ConverterChildProcessId + Stop-ConverterProcessTree; the timeout path now kills the whole tree, waits for confirmation, and reports surviving PIDs.
bim-streaming-server/tests/test_host_native_conversion_service.py Moves converter doubles from the subprocess.run seam to subprocess.Popen; adds tree-kill, pin-ordering, and unprovable-containment tests.

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 19cec832db

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread bim-streaming-server/scripts/convert-ifc-to-usdc.ps1 Outdated
Comment thread bim-streaming-server/scripts/convert-ifc-to-usdc.ps1 Outdated

@codex-tri-adversarial-bot codex-tri-adversarial-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex Tri-Adversarial Bot

Automated tri-adversarial ship-gate (L0 terra triage / L1 tier-routed lens fanout / L2 refute-by-default / L3 sol apex — Codex models).
Mapped event: COMMENT


Codex Tri-Adversarial ship-gate — PR #509

  • Repo head: fix/489a-converter-tree-containment @ 19cec83
  • Base: main @ 970dc34
  • Files changed: 3
  • Engine: four-model tri-adversarial gate on Codex — L0 triage gpt-5.6-terra/low; L1 lens finders routed gpt-5.6-terra/low → gpt-5.6-luna/medium → gpt-5.5/xhigh (security floor gpt-5.5); L2 refute-by-default gpt-5.5/xhigh, top-tier findings refuted by gpt-5.6-sol/xhigh (every refutation cross-model); L3 apex gpt-5.6-sol/max. 誠實聲明:層級與 Claude 三層 gate 同構(terra≈haiku、luna≈sonnet、gpt-5.5≈opus、sol≈fable),但模型池是 Codex 的,非 Anthropic 的。

Verdict

SHIP

  • 阻擋門檻 severity: critical, high
  • mapped GitHub event: COMMENT
  • ℹ️ 判定為 SHIP,但刻意不送 APPROVE:GitHub App 的 approving review 不計入 required_approving_review_count(2026-07-31 實測)。本報告是證據,approving 那一票請由真人帳號投。

Difficulty & routing

  • overall: critical (source: terra-triage)
  • lens tiers: correctness→gpt-5.5, security→gpt-5.5, simplification→gpt-5.5, test-gap→gpt-5.5

Layer stats

  • L1: raw=7 deduped=7 finder_failures=0
  • L2: confirmed=5 refuted=2 unverified=0
  • L3 final: 5

Findings (final, after apex)

[medium] PowerShell timeout can still block on unbounded final WaitForExit

  • id: L1-COR-001 lens: correctness file: bim-streaming-server/scripts/convert-ifc-to-usdc.ps1
  • provenance: finder=gpt-5.5 refuter=gpt-5.6-sol (cross-model-guard) L2=confirmed
  • evidence: The bounded timeout branch calls Stop-ConverterProcessTree, but the unchanged finally path subsequently invokes parameterless $process.WaitForExit() unconditionally.
  • why: WaitForExit() has no deadline. A surviving root process can block it directly, while an untracked descendant retaining inherited redirected-output handles can delay asynchronous stream completion indefinitely. The script may therefore never reach its timeout error.
  • proposed fix: On timeout, replace the final wait/drain with a bounded operation and cancel or close asynchronous readers when the deadline expires; do not invoke parameterless WaitForExit() after containment has already reported survivors.

[medium] Windows launch can resume a converter after Job assignment failed

  • id: L1-COR-002 lens: correctness file: bim-streaming-server/source/extensions/ezplus.bim_review_stream.messaging/ezplus/bim_review_stream/messaging/ifc2usdc_powershell_adapter.py
  • provenance: finder=gpt-5.5 refuter=gpt-5.6-sol (cross-model-guard) L2=confirmed
  • evidence: assigned = api.assign_process(...) is followed by api.resume_process_threads(pid) before the not assigned branch closes the Job handle and sets job = None.
  • why: The child is still suspended when assignment failure is known, so resuming it discards the last race-free failure point. The later process-tree snapshot is not equivalent containment: the running converter can create children during enumeration, and descendants can become undiscoverable after their parent exits.
  • proposed fix: If assignment fails, terminate and reap the still-suspended child, close the Job handle, and raise a launch/containment error without resuming any thread.

[medium] PowerShell process-tree killer is not directly covered by the added tests

  • id: L1-TG-001 lens: test-gap file: bim-streaming-server/scripts/convert-ifc-to-usdc.ps1
  • provenance: finder=gpt-5.5 refuter=gpt-5.6-sol (cross-model-guard) L2=confirmed
  • evidence: The real-process regression test replaces _build_converter_command with a Python fixture, so it never loads convert-ifc-to-usdc.ps1; the other new timeout tests use fake Popen or mocked terminate_tree behavior.
  • why: No added automated test exercises Get-ConverterChildProcessId, Stop-ConverterProcessTree, its platform-specific discovery, leaf-first termination, bounded survivor polling, or timeout-message construction. A defect in this independently implemented containment path can therefore pass the visible suite.
  • proposed fix: Add a PowerShell-level regression test that launches a child and grandchild, invokes the actual timeout/tree-kill path, and asserts bounded completion, both processes gone, and accurate survivor reporting when termination is forced to fail.

[medium] Job-object containment launch fallback lacks adversarial tests

  • id: L1-TG-002 lens: test-gap file: bim-streaming-server/source/extensions/ezplus.bim_review_stream.messaging/ezplus/bim_review_stream/messaging/ifc2usdc_powershell_adapter.py
  • provenance: finder=gpt-5.5 refuter=gpt-5.6-sol (cross-model-guard) L2=confirmed
  • evidence: The diff adds branches for breakaway-denied retry, failed Job assignment, and zero resumed threads, but the new tests exercise only the host's normal path or mock timeout-time containment.
  • why: These launch transitions determine whether the converter ever executes inside a provable boundary and whether suspended children and handles are cleaned up. The assignment defect survived because no deterministic fault-injection test covers these branches.
  • proposed fix: Use fake _Win32ContainmentApi and Popen objects to verify breakaway retry flags, assignment-failure termination before resume, resume-failure termination/reaping, handle closure, and converter_unavailable mapping.

[low] POSIX process-group liveness can mistake zombies for surviving work

  • id: L1-COR-003 lens: correctness file: bim-streaming-server/source/extensions/ezplus.bim_review_stream.messaging/ezplus/bim_review_stream/messaging/ifc2usdc_powershell_adapter.py
  • provenance: finder=gpt-5.5 refuter=gpt-5.6-sol (cross-model-guard) L2=confirmed
  • evidence: _posix_group_alive() treats every successful os.killpg(pgid, 0) as live, while _reap_bounded() can reap only the direct child and the deadline path reports (pid,) rather than identifying the remaining group member.
  • why: A zombie descendant can keep the process group discoverable even though it cannot execute input. With a delayed or non-reaping PID 1/subreaper, this produces a false converter_containment_failed and can report the already-reaped root PID. Severity is reduced to low because the behavior remains fail-closed and does not permit continued execution.
  • proposed fix: On Linux, inspect process-group members and treat the group as non-executing only when every remaining member is demonstrably in zombie state; retain conservative behavior on platforms where state cannot be established and report actual members when possible.

Killed (did not survive L2/L3)

  • L1-SIM-001 [low] Converter child-walk logic is duplicated instead of using the existing platform helper — The finding’s own proposed alternative—document why the converter intentionally keeps a private copy—is already satisfied by the added comment: it identifies the existing helper and explains that importing it would couple the standalone converter to deploy-lib. The finder also admits it did not insp
  • L1-SIM-002 [low] Tests keep a compatibility wrapper that now just delegates to the new fake Popen helper — The wrapper remains an intent-specific test helper: it exposes only returncode/stdout/stderr, hides _patch_popen’s timeout/on_start/capture mechanics, and lets existing result-oriented tests remain unchanged during a security-focused seam migration. The diff does not establish that its remaining c

Summary

All five survivors remain actionable; L1-COR-003 is downgraded to low because it is a fail-closed false positive rather than a containment escape. The primary fixes are to keep the PowerShell timeout path bounded and to abort a suspended Windows launch when Job assignment fails. Add direct PowerShell and Win32 fault-injection regression coverage alongside those fixes.

Agent calls

  • 13/13 ok, engine wall-clock 627.7s

VERDICT

SHIP

VERDICT: SHIP

@monkey1sai

Copy link
Copy Markdown
Owner Author

補充 runtime 佐證 → gate 報告的 L1-COR-001(medium,PowerShell final WaitForExit 無界阻塞):

本 session 對真實 89.4MB IFC 的轉檔實測恰好重現了這個機制——兩次在多 agent 高負載並行下,USDC 於 ~2 分鐘完整產出後 wrapper 逾 10 分鐘不返回(被殺);單獨重跑(run3)與 origin/main baseline 皆 34–35s 乾淨退出。與 finding 的機制吻合:無參 WaitForExit() 等待 async output stream drain,任何繼承了 redirected handle 的殘留 descendant 都會無界延遲它;高負載下 Kit 的 helper 子程序 lingering 機率上升。

注意這段 finally 路徑是未變更的既有碼(main 同樣存在),非本 PR 引入;本 PR 的 timeout 分支已有 bounded-deadline 測試鎖定。建議依 gate 的 proposed fix(bounded wait+deadline 到期時關閉 async readers)另開 follow-up 小 PR 處理,不擴大本 PR 範圍。

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: daaf3bf15c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread bim-streaming-server/scripts/convert-ifc-to-usdc.ps1 Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d6f791b350

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

monkey1sai and others added 2 commits August 12, 2026 12:40
…489)

PR #509 review round 2. Each fix is scoped to the containment boundary the
change already promises; the review findings that are pre-existing or not
closable from inside this PR are answered on the threads instead.

- Windows launch is now fail-closed: when the Job Object cannot be created or
  AssignProcessToJobObject fails, the still-suspended child is terminated and
  the launch raises (-> converter_unavailable) instead of silently degrading to
  a racy PID-tree snapshot with no containment boundary.
- POSIX close() terminates the process group. POSIX has no kill-on-close
  equivalent and terminate_tree() only runs for TimeoutExpired, so a converter
  that exited early (crash, external kill, non-zero exit) could leave Kit
  grandchildren running while the HOOPS pin was released.
- POSIX liveness ignores defunct members. killpg(pgid, 0) also succeeds for a
  group whose members are all zombies; on hosts where PID 1 does not reap
  orphans that burned the whole containment deadline and reported a false
  converter_containment_failed.
- converter_containment_failed now lists the live group members discovered from
  /proc instead of the direct child PID, which is usually the process that has
  already exited.
- The POSIX grace period is granted to the whole group: the code used to
  SIGKILL as soon as the direct child was reaped, truncating descendant signal
  handlers.
- Timeout and containment failures carry the Kit log diagnostics
  (kit_stdout_log / kit_stderr_log + output tail) that
  streaming-ifc-usdc-conversion-authority requires for any post-start failure;
  all three post-start failure paths now share one helper.
- convert-ifc-to-usdc.ps1 re-discovers descendants through termination instead
  of trusting a single BFS snapshot, and no longer counts defunct PIDs as
  survivors. The reparenting case remains out of reach from inside the script;
  the authoritative boundary is the caller's Job Object / process group, and
  that limit is now documented rather than implied away.
- The _ContainedProcess docstring states the real boundary strength per
  platform: the Windows Job Object is non-escapable, the POSIX process group is
  best-effort and a descendant that calls setsid() leaves it.

Validation:
- .venv pytest bim-streaming-server/tests/test_host_native_conversion_service.py
  -> 114 passed, 6 skipped (baseline 104/6; 10 new tests, all red before the fix)
- pwsh bim-streaming-server/scripts/tests/test-convert-ifc-to-usdc.ps1 -> passed
  (new: AST-lifted containment helpers, live 2-deep tree, late-descendant
  rescan; the rescan case was verified red against the pre-fix function, which
  returned survivors=[] with the late child still alive)
- Windows host-native Kit re-run at this head: real 89.4MB IFC
  (storage/demo_lib_2026.ifc) -> EXIT=0, 39s, 28,462,810 bytes, byte-identical
  to the recorded baseline

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review


P1 Badge Propagate inner containment failures with the dedicated code

When Kit reaches the PowerShell timeout and Stop-ConverterProcessTree reports surviving PIDs or fails to reach a fixed point, the script throws and exits during the outer adapter's intentional 30-second buffer, so timed_out remains false and this branch always raises converter_failed. The newly introduced converter_containment_failed code is therefore used only when PowerShell itself exceeds the outer timeout, while the usual inner containment-failure path is misclassified and downstream job/API diagnostics cannot distinguish a potentially live process tree from an ordinary conversion error.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread bim-streaming-server/scripts/convert-ifc-to-usdc.ps1 Outdated
Comment thread bim-streaming-server/tests/test_host_native_conversion_service.py
monkey1sai and others added 2 commits August 12, 2026 12:47
…e overlapping containment hunks)

Two agents worked the same PR #509 review threads. The origin implementation is
kept verbatim for every overlapping hunk (fail-closed Windows launch, POSIX
close() sweep, /proc-based group membership, ps1 discover->kill fixed point);
the local-only deltas are re-applied in the following commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds the review findings that the concurrent origin commit did not cover. The
overlapping hunks (fail-closed Windows launch, POSIX close() sweep, /proc-based
group membership, ps1 discover->kill fixed point) are origin's implementation,
kept verbatim.

- POSIX grace period is granted to the whole GROUP. _reap_bounded returns the
  moment PowerShell is reaped, so the old code escalated to SIGKILL within
  milliseconds and a Kit/HOOPS descendant still running its SIGTERM handler was
  killed mid-cleanup; the configured grace was never actually granted. Reap the
  direct child, then poll the group until the grace deadline.
- Timeout and containment failures now carry the Kit log diagnostics.
  streaming-ifc-usdc-conversion-authority requires kit_stdout_log /
  kit_stderr_log plus a tail summary for any failure after the subprocess
  starts, and names timeout explicitly; both timeout raises had neither, so
  conversion_authority._fail_job could only surface an error with no diagnostic
  path. All three post-start failure paths now share _failure_diagnostics().
  The ps1 prints ##CONV_META## before Kit starts, so the paths survive the kill.
- convert-ifc-to-usdc.ps1 no longer counts defunct PIDs as containment
  survivors. Get-Process keeps returning a zombie, so on hosts whose PID 1 does
  not promptly reap orphans the wait burned the full deadline and then reported
  a containment FAILURE for a tree that was already dead. Test-ConverterProcessAlive
  reads /proc/<pid>/stat and excludes state Z; the state field is taken after
  the last ')' because comm may contain spaces and parens.
- _ContainedProcess documents the real per-platform boundary strength: the
  Windows Job Object is non-escapable (no JOB_OBJECT_LIMIT_BREAKAWAY_OK), the
  POSIX process group is best-effort and a descendant that calls setsid() leaves
  it. Closing that residual needs a cgroup and is out of scope here; the fix is
  to stop implying a guarantee POSIX does not give.
- Module-level _POSIX_SIGTERM/_POSIX_SIGKILL so the POSIX branches stay unit
  testable from a Windows host instead of only being verifiable on Linux.
- convert-ifc-to-usdc.ps1 gets its first executable coverage: the containment
  helpers are lifted out through the PowerShell AST and driven against a real
  2-deep process tree plus a late-appearing descendant.

Validation:
- .venv pytest bim-streaming-server/tests/test_host_native_conversion_service.py
  -> 113 passed, 7 skipped (baseline 104/6; the 7th skip is origin's
  SIGKILL-requires-POSIX test, not a Windows regression)
- pwsh bim-streaming-server/scripts/tests/test-convert-ifc-to-usdc.ps1 -> passed
  (the late-descendant case was verified red against the pre-review function,
  which returned survivors=[] with the late child still alive)
- Windows host-native Kit re-run at this head: real 89.4MB IFC
  (storage/demo_lib_2026.ifc) -> EXIT=0, 44s, 28,462,810 bytes, byte-identical
  to the recorded baseline

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 86a52a5baa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 397fbe327a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread bim-streaming-server/scripts/tests/test-convert-ifc-to-usdc.ps1 Outdated
monkey1sai and others added 2 commits August 12, 2026 13:16
… test probe (#489)

PR #509 review round 2. Verdicts: adapter:599 fixed, test:3687 fixed, ps1:427
refuted with executable evidence.

- _windows_live_pids returns None when QueryInformationJobObject fails, and
  _terminate_tree_windows can no longer satisfy its success condition from a
  PPID walk. The Job Object is the authoritative membership record; ancestry is
  a different, weaker question, and the root may already be gone with its
  descendants reparented, so an empty walk was being promoted into a
  containment proof for a job whose members were never observed. A single
  failed query may be transient, so the poll continues to the deadline and only
  then fails closed; the walk is still used, but purely to name suspect PIDs in
  the error, never to prove emptiness.
- _pid_is_alive in the tests excludes defunct processes. os.kill(pid, 0)
  succeeds for a corpse, while the production containment deliberately treats
  zombies as terminated, so on a host whose PID 1 does not promptly reap
  orphans the probe would have failed
  test_converter_timeout_kills_the_whole_process_tree_before_raising while
  containment had in fact succeeded. Parsed independently of the adapter.
- ps1:427 (orphan forked between enumeration and the reverse-order kill) does
  NOT apply to Windows. Verified on a real host: Windows does not reparent
  orphans, Win32_Process.ParentProcessId still resolves to the killed parent,
  and the loop re-walks from every KNOWN pid, so the orphan is rediscovered and
  terminated before a fixed point is declared. Locked in by a new 3-level
  (root -> mid -> leaf) test where the mid process forks late and is then
  killed; a root-only re-walk was measured against the same fixture and
  reported FixedPointReached=True with the orphan still alive, so the test
  discriminates. Linux does reparent, which destroys that link, so
  Test-OrphanRediscoverySupported now gates the "terminated and confirmed gone"
  wording and the POSIX message states that containment is not proven by the
  script there (the caller's process-group boundary is authoritative; cgroup
  work tracked in #517).

Validation:
- .venv pytest bim-streaming-server/tests/test_host_native_conversion_service.py
  -> 118 passed, 7 skipped (previous round 113/7)
- pwsh bim-streaming-server/scripts/tests/test-convert-ifc-to-usdc.ps1 -> passed
- Windows host-native Kit re-run at this head: real 89.4MB IFC -> EXIT=0, 44s,
  28,462,810 bytes, byte-identical to the recorded baseline

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…claim

Third concurrent collision on this branch. origin's implementation is kept for
every overlapping hunk (immediate fail-closed on unreadable job membership plus
the containment_detail reason code, and the zombie-aware test liveness probe);
this merge only re-adds the local-only work and corrects one factual claim.

origin's Stop-ConverterProcessTree comment and its timeout message stated that a
PPID walk loses an orphan whose parent died "to init/PID 1 on Linux, to nothing
on Windows". That is measured to be false on Windows: an exited process keeps
being named by its children's Win32_Process.ParentProcessId, so querying children
of an already-killed KNOWN pid still returns the orphan, which is exactly why the
loop re-walks from every known member. Probe on a real host:
ORPHAN_STILL_DISCOVERABLE_FROM_DEAD_PARENT=True. The comment and the Windows
timeout wording now say what was measured; the Linux half of origin's claim is
correct and is kept, gated behind Test-OrphanRediscoverySupported.

Re-added on top of origin's version:
- scripts/tests/test-convert-ifc-to-usdc.ps1 case 2: a 3-level root -> mid -> leaf
  fixture where the leaf is forked late and the mid process is killed, asserting
  the leaf is terminated. The same fixture driven by a root-only re-walk was
  measured reporting FixedPointReached=True with the leaf still alive, so the
  case discriminates rather than passing vacuously.
- test_windows_containment_failure_names_the_live_job_members: readable and
  non-empty membership, the third case alongside origin's unreadable and
  readable-empty ones.
- test_liveness_probe_helper_treats_a_posix_zombie_as_dead: locks in origin's
  _pid_is_alive zombie fix, which had no test.

Validation: pytest 118 passed / 7 skipped; ps1 harness passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
monkey1sai added a commit that referenced this pull request Aug 12, 2026
…spend one budget

Second Codex tri-adversarial round on PR #513 returned NO-SHIP on three more
findings against Stop-HostNativeProcessTreeAndWait. All three are real; none
was refuted.

L1-COR-001 (high): the exited-parent sweep added in 110c657 is only sound where
the OS keeps the creator PID on an orphan. Windows does - measured here, a
killed parent's PID still resolves its orphaned child and its conhost through
Win32_Process.ParentProcessId, and the PID cannot be recycled while this call
holds the exited process handle. Linux does not: the kernel re-parents orphans
to init or the nearest subreaper, so both discovery passes return empty, and an
empty pass was being read as containment. That platform fact now lives in
Test-OrphanRediscoverySupported alongside the other platform primitives, and it
is the same conclusion the converter containment work reached in #509, which
had to own a job object / process group established at launch rather than trust
a PPID link.

Where rediscovery is unsupported and the parent had already exited on entry,
the helper now fails closed and says so: containment is not provable via PPID
on this platform, and the authoritative boundary is the caller. A new
-KnownDescendantProcessIds parameter is the escape hatch - a descendant record
captured before the parent could exit replaces the link discovery lost, and the
helper then contains and proves that set normally.

L1-SEC-001 (high): the loop broke cleanly only when survivors AND newly
discovered descendants were both zero, but the deadline branch broke on time
alone and the post-loop check only tested survivors. A deadline pass that
discovered a descendant and successfully stopped it therefore left zero
survivors and returned success, with no fixed point ever reached. Success now
requires a recorded clean pass; reaching the deadline without one throws.

L1-COR-002 (medium): TimeoutMs was not one budget. The stopwatch started after
discovery and the parent wait still received the full allowance. It now starts
before discovery, the parent wait receives only the remainder, and no further
containment pass begins on an exhausted budget.

Verified against the previous implementation to confirm these are real, not
theoretical: the churn scenario returned SUCCESS after discovering twelve
descendants, and the slow-discovery scenario spent 2786ms against an advertised
1500ms bound. Both now fail closed and stay inside the budget.

Four new behavioural cases: production defaults against an already-exited
parent asserting whichever branch this platform is on (no injected lookup), the
POSIX fail-closed path driven from a Windows run via the injected capability
gate, the pre-exit record proving containment on that same simulated platform,
and one case each for the clean-pass requirement and the end-to-end budget.

The gated deployment static test's three pinned literals are unchanged; the
parent wait keeps `$Process.WaitForExit($TimeoutMs)` honest by living in a
nested helper whose own TimeoutMs parameter IS the remaining allowance.

Refs #489.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 145381ab21

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread bim-streaming-server/scripts/convert-ifc-to-usdc.ps1 Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b483e87798

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread bim-streaming-server/scripts/convert-ifc-to-usdc.ps1

@monkey1sai-blip monkey1sai-blip left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved by monkey1sai-blip (the reviewer account pinned by the repo's merge governance).

Submitted through scripts/blip_review.py — a scripted approval carrying the operator's authority, pinned to head 841277a7c6e8f4319e4f32e764e5b2ab8b8654b0. This is the mechanism the GitHub App cannot satisfy: an App's approving review does not count toward required_approving_review_count.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 841277a7c6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread bim-streaming-server/tests/test_host_native_conversion_service.py
Comment thread bim-streaming-server/scripts/convert-ifc-to-usdc.ps1
monkey1sai added a commit that referenced this pull request Aug 12, 2026
…489-B) (#513)

* fix(deploy): resolve Kit control URL after the .env missing-key merge

The Phase 1 audit sealed $resolvedKitControlUrl and the Kit Manager runtime
signature before the Phase 2 ".env / .env.example missing-key merge" ran. That
merge can both append a missing KIT_CONTROL_URL and repoint $resolvedEnvFile
from the .example to the real env file, so a run that repaired the file still
started the host-native Kit Manager with the stale pre-merge value and then
persisted a signature describing the repaired state — a blocked
runtime-control state that reads as configured on the next run.

Pure relocation, not duplication: both assignments move down to immediately
after the env merge and volume fix. Nothing between the old and new positions
reads either variable, so the exactly-once AST guarantee asserted by
test-deploy-governance-static.ps1 is preserved. $resolvedAllowedStageHosts is
deliberately left where it is.

test-deploy-governance-static.ps1 gains index-ordering assertions that pin the
resolve, the signature build and the child launch after the merge marker.

Known delta: -DryRun exits before Phase 2, so it no longer validates
KIT_CONTROL_URL. The value it used to validate was the pre-merge one read from
whichever file the audit resolved, which is the stale read this change removes.

Refs #490

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(verify): assert Kit control URL locality in the Deployment profile

$expectedKitControlUrl was read verbatim from kit-manager-api.params.json and
only string-compared against the /health payload's kit_control_url. The
signature checks cover port and revision equality, which prove checkout
identity and nothing about URL policy, and the local-address assertion was
applied to host_native_bind_host and the conversion health host but never to
this URL. A REMOTE origin agreed between a drifted signature and a drifted
service therefore passed silently.

Assert-DeploymentKitControlUrlIsLocal now runs beside the two existing asserts
inside the same -not $PlanOnly guard. It deliberately re-implements the rule
from Resolve-HostNativeKitControlUrl rather than importing it — the verifier is
an independent layer, exactly as Assert-DeploymentHostNativeBindIsLocal
re-implements Test-HostNativeLocalAddress. Empty stays allowed as the honest
unconfigured state, localhost is accepted because the launcher canonicalises it
through, and non-literal hosts are refused without DNS resolution so the
verifier cannot be rebound.

test-verify-all.ps1 gains a locality accept/reject matrix, subprocess rejection
cases for a remote and a credentialed control URL, acceptance runs for
127.0.0.1 and localhost, an AST assertion that the verifier never calls the
launcher resolver, and a paired case proving that a matching-but-remote health
payload satisfies the identity comparison on its own.

Refs #491

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(launcher): contain descendants in Stop-HostNativeProcessTreeAndWait

Kill($true), WaitForExit and HasExited all describe the SAME Process object.
Microsoft documents that they can report completion while descendants are still
running, and the old catch additionally swallowed the tree-kill exception
whenever the parent had already exited — precisely the case where orphaned
grandchildren survive. The CAD hardener and the Kit Manager import probe use
this helper to prove a timed-out tree is gone before releasing the trust
boundary they hold, so the postcondition was false where it mattered most.

Second defect in the same helper: Process.Kill([bool]) does not exist on .NET
Framework, so under Windows PowerShell 5.1 the tree kill ALWAYS threw into that
catch and the helper silently degraded to parent-only termination.

The helper now snapshots the descendant PID set through
Get-PlatformChildProcessIds before terminating (afterwards the parent/child
links are gone and orphans are re-parented), guards the tree kill behind an
overload probe with the enumerated-PID fallback Stop-HostNativeService already
uses, and waits for the parent AND every snapshotted descendant inside one
bounded budget. Anything still alive throws instead of reporting success.
Descendant liveness is judged on process identity, not the bare PID, so a
recycled PID reads as "our descendant is gone" rather than failing a caller
closed on an unrelated process.

test-host-native-launcher.ps1 gains a dynamic hung-fixture case proving both
recorded PIDs exited inside the bounded window, a negative case with an
injected unkillable descendant proving the helper fails closed, and
source-shape assertions pinning the overload guard.

This commit also registers the bundle's single open ledger entry
`mechanism-hardening-2`, covering the three verification-mechanism paths this
pull request changes (#490 deploy.ps1, #491 verify-all.ps1, #489
host-native-launcher.ps1). One entry, one canonical Linux rebuild at fixpoint,
instead of three serialised debts for one hardening round.

Refs #489

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(launcher): prove containment for exited parents and legacy fallbacks

The PR #513 Codex tri-adversarial ship-gate returned NO-SHIP on four
findings against Stop-HostNativeProcessTreeAndWait, and four PR review
threads named the same defects.

L1-COR-001 (high): `if ($Process.HasExited) { return }` ran before the
descendant snapshot, so a parent that lost the race between the caller's
liveness decision and this helper took the entire sweep with it while its
descendants kept running. Neither OS cascades termination, so an exited
parent now excuses the parent kill/wait only - the snapshot, the
termination and the survivor proof still run.

L1-COR-002: both fallback loops stopped snapshotted descendants by bare
PID. A PID recycled between enumeration and the stop meant terminating an
unrelated host process while the survivor poll still read "our descendant
is gone". Every stop is now identity-revalidated immediately before it
fires, and a changed incarnation is treated as already gone.

L1-SEC-002: one fixed snapshot is not containment - a snapshotted process
can spawn another child before it dies, and that child was in neither the
stop list nor the success check. Containment is now a bounded fixed point
that re-enumerates, terminates and verifies until a pass finds nothing new
and nothing alive, and fails closed at the deadline. Re-enumeration
expands only from roots that are still the incarnation we recorded; the
parent stays a root because this call holds its Process handle.

L1-TG-003: the tree-kill capability decision is injectable, so the .NET
Framework / Windows PowerShell 5.1 fallback is driven as behaviour from a
PowerShell 7 run instead of by source-string order alone.

Six behavioural cases cover it: an already-exited parent with a real
orphaned descendant, the same shape failing closed, an identity-gated stop
on a recycled PID, a forced no-Kill(bool) fallback over a real three-level
chain (deepest-first, parent terminated, every PID proven gone), a
post-snapshot spawn, and the fallback's own fail-closed path.

Two more findings from the same review round:

- `deploy.ps1 -DryRun` stopped adjudicating KIT_CONTROL_URL when the
  authoritative resolution moved after the Phase 2 missing-key merge. That
  merge only appends an absent key with a default and can never repair an
  existing value, so an unusable authority is now a Phase 1 hard fail,
  reported without echoing the URL, with the authoritative post-merge
  resolution left where it is.
- scripts/tests/test-host-native-launcher.ps1 ran in no workflow, so these
  regressions gated nothing. It now runs in the required
  rebuild-test-deploy job, PowerShell 7 only: the dynamic fixtures use
  ProcessStartInfo.ArgumentList, which .NET Framework does not have.

Refs #489.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(evidence): bind mechanism-hardening-2 bootstrap evidence to its reviewed head

The PR #513 review threads found this bundle's evidence unbindable: it
recorded only the baseline commit, so no reader could tie the PASS results
to the code under review, and the PASS lines carried bare command ids with
no invocation or command-map reference.

The record now splits into the two rounds that produced it, pins the
reviewed head commit `110c657` with the clean-worktree result observed at
it, names the immutable command map that resolves the ids, and writes the
resolved invocation inline for the three ids that map does not carry.

That third point also corrects a partial disclosure: the earlier note
covered `test-deploy-dryrun` and `test-deploy-env-fallback` but omitted
`test-stop-all-single-pid`, which is in the same position. All three have
executable sources under scripts/tests/ and pass; none is a
verification-contract command id. Promoting them into the contract would
enlarge this entry's fixpoint obligation, so that decision is left to the
ledger owner rather than taken inside a review-fix round.

Refs #489.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(launcher): gate orphan sweeps by platform, require a clean pass, spend one budget

Second Codex tri-adversarial round on PR #513 returned NO-SHIP on three more
findings against Stop-HostNativeProcessTreeAndWait. All three are real; none
was refuted.

L1-COR-001 (high): the exited-parent sweep added in 110c657 is only sound where
the OS keeps the creator PID on an orphan. Windows does - measured here, a
killed parent's PID still resolves its orphaned child and its conhost through
Win32_Process.ParentProcessId, and the PID cannot be recycled while this call
holds the exited process handle. Linux does not: the kernel re-parents orphans
to init or the nearest subreaper, so both discovery passes return empty, and an
empty pass was being read as containment. That platform fact now lives in
Test-OrphanRediscoverySupported alongside the other platform primitives, and it
is the same conclusion the converter containment work reached in #509, which
had to own a job object / process group established at launch rather than trust
a PPID link.

Where rediscovery is unsupported and the parent had already exited on entry,
the helper now fails closed and says so: containment is not provable via PPID
on this platform, and the authoritative boundary is the caller. A new
-KnownDescendantProcessIds parameter is the escape hatch - a descendant record
captured before the parent could exit replaces the link discovery lost, and the
helper then contains and proves that set normally.

L1-SEC-001 (high): the loop broke cleanly only when survivors AND newly
discovered descendants were both zero, but the deadline branch broke on time
alone and the post-loop check only tested survivors. A deadline pass that
discovered a descendant and successfully stopped it therefore left zero
survivors and returned success, with no fixed point ever reached. Success now
requires a recorded clean pass; reaching the deadline without one throws.

L1-COR-002 (medium): TimeoutMs was not one budget. The stopwatch started after
discovery and the parent wait still received the full allowance. It now starts
before discovery, the parent wait receives only the remainder, and no further
containment pass begins on an exhausted budget.

Verified against the previous implementation to confirm these are real, not
theoretical: the churn scenario returned SUCCESS after discovering twelve
descendants, and the slow-discovery scenario spent 2786ms against an advertised
1500ms bound. Both now fail closed and stay inside the budget.

Four new behavioural cases: production defaults against an already-exited
parent asserting whichever branch this platform is on (no injected lookup), the
POSIX fail-closed path driven from a Windows run via the injected capability
gate, the pre-exit record proving containment on that same simulated platform,
and one case each for the clean-pass requirement and the end-to-end budget.

The gated deployment static test's three pinned literals are unchanged; the
parent wait keeps `$Process.WaitForExit($TimeoutMs)` honest by living in a
nested helper whose own TimeoutMs parameter IS the remaining allowance.

Refs #489.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(evidence): record the round 3 containment fixes and their measured basis

Binds the second ship-gate round to its reviewed head d634530 with the
clean-worktree result observed at it, and records what was actually measured
rather than argued:

- Windows orphan rediscovery is SUPPORTED. A killed fixture parent's PID still
  resolved its orphaned python child and its conhost through
  Win32_Process.ParentProcessId, and the production-default sweep contained and
  proved both. That is the platform fact Test-OrphanRediscoverySupported
  encodes, and the reason the POSIX branch has to fail closed instead.
- Both behavioural regressions were reproduced against the previous
  implementation before fixing: the clean-pass scenario returned SUCCESS after
  discovering twelve descendants, and the slow-discovery scenario spent 2786 ms
  against an advertised 1500 ms bound.

The POSIX branch of the platform gate is proven on this host through the
injected capability decision only; no canonical Linux session was opened, so it
has never run on a real re-parenting kernel. That is recorded as an explicit
NOT_RUN rather than folded into the PASS list.

Refs #489.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(launcher): narrow the process-tree helper's claim to what it can prove

Three ship-gate rounds all landed HIGH findings on the same thing: the helper
was documented and used as if it delivered inescapable containment, while every
mechanism it has rides the OS parent/child link, which is advisory. Patching the
next escape window would have invited a fourth. This narrows the claim instead.

The contract is now stated in the function itself: a bounded best-effort SWEEP
with a fail-closed provability report. It enumerates what it can reach,
terminates deepest-first with identity revalidation, re-enumerates to a fixed
point, and throws whenever it cannot prove the set it knows about is gone.
It is explicitly NOT an escape-proof boundary - only an OS boundary established
at LAUNCH (Windows Job Object, POSIX process group / cgroup) can be that, which
is #517 and the Start-HostNativeService follow-up. A caller gets "this sweep
proved what it could see, or it threw", never "nothing survived".

Removed -KnownDescendantProcessIds. It was the escape hatch that let the helper
keep claiming provable containment on a re-parenting platform, neither
production caller passed it, and its existence blurred exactly the line this
round is drawing. The POSIX already-exited-parent path stays a fail-closed
throw, and the message now states the narrowed contract and names the launch-
time boundary and #517 rather than offering a parameter as the answer.

The gate's remaining HIGH - a descendant spawned between enumeration and the
stop that follows it - is REFUTED by measurement, not argument. A real
three-level fixture whose grandchild is hidden from the snapshot (running the
whole time, so it stands in for one spawned a moment after enumeration), with
the tree-kill capability forced off so .NET cannot do the containment for us:
the helper stopped the snapshot, killed the parent last, and pass 1 re-walked
every snapshot member it had just killed, rediscovered the grandchild through
its dead parent's link, stopped it, and pass 2 came back clean. All three PIDs
gone, no throw. That is now a regression test. The lookup sequence it asserts on
is the mechanism: [P, D, P, D, conhost, G, P].

The residual that survives that refutation is documented rather than papered
over, because closing it would make things worse: a descendant discovered AND
stopped inside one pass drops out of the expansion roots, so a child it spawned
in that sub-window is not rediscovered. Expanding from dead PIDs every pass
would trade this fail-open gap for a fail-dangerous one - a recycled PID would
contribute an unrelated process's children to the kill set.

Also: the orphan-rediscovery test no longer asks Test-OrphanRediscoverySupported
which branch to expect, which was the implementation grading its own homework.
It reads the raw OS record instead - Win32_Process.ParentProcessId on Windows,
/proc/<pid>/stat field 4 on Linux - and asserts the helper agrees with it.

The gated deployment static test's three pinned literals are unchanged.

Refs #489, #517.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(evidence): record round 4, the HIGH-2 refutation and the residual it leaves

Binds the narrowed-claim round to its reviewed head 3044e78 with the clean
worktree observed at it, and keeps the three kinds of statement apart instead of
flattening them into one PASS list:

- MEASURED: a descendant hidden from the snapshot but running throughout was
  rediscovered, stopped and proven gone by the re-enumerating fixed point, with
  the tree kill forced off so .NET could not do the work. That refutes the gate's
  remaining HIGH and is now a regression case.
- ANALYSIS-NOT-MEASURED: the sub-window that survives it - a descendant
  discovered and stopped inside one pass leaves the expansion roots, so a child
  it spawned in that window is not rediscovered. Left open deliberately, because
  expanding from dead PIDs would trade a fail-open gap for a fail-dangerous one.
- CONTRACT: the helper is a bounded best-effort sweep with a fail-closed
  provability report, not an escape-proof boundary, and -KnownDescendantProcessIds
  is gone.

The POSIX leg of the platform gate is still NOT_RUN on a real re-parenting
kernel; it remains proven only through the injected capability decision.

Refs #489, #517.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(env): 補上 Kit 控制網址樣板佔位

* chore(governance): declare platform-adapter.ps1 in the mechanism-hardening-2 entry

Round-3 added Test-OrphanRediscoverySupported to
scripts/lib/platform/platform-adapter.ps1, which is a classified
verification-mechanism path; the bundle's ledger entry must declare every
mechanism path its diff touches. test-self-referential-bootstrap all green.

Refs #489

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(deploy): 保留明示環境檔並封閉子程序列舉失敗

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: monkey1sai-blip <monkey1sai@icloud.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d930d8840e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread bim-streaming-server/scripts/convert-ifc-to-usdc.ps1

@monkey1sai-blip monkey1sai-blip left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@claude 最後確認 + opus 5 max 交叉對抗驗證

@claude

claude Bot commented Aug 12, 2026 •

Copy link
Copy Markdown

Claude finished @monkey1sai-blip's task in 5m 16s —— View job


Claude finished @monkey1sai-blip's task

Todo

  • Gather context: read current diff (origin/main...HEAD) and full round-by-round review history
  • Re-verify each "已修" claim against actual current code (not just commit messages)
  • Cross-adversarial pass on remaining/unaddressed threads
  • Post final verdict + findings

Scope of this pass

This is a review/confirmation request ("最後確認 + opus 5 max 交叉對抗驗證"), so I read the current code at HEAD (841277a..4162876, no further changes to the two production files since 841277a) and adversarially re-checked it — I did not modify anything.

I re-verified the core containment path end-to-end (_run_powershell_conversion → _spawn_contained_converter → terminate_tree → close()) and it holds up:

  • terminate_tree() now runs unconditionally on every exit path (success, non-zero exit, timeout) inside the try block at ifc2usdc_powershell_adapter.py:2543, not just on TimeoutExpired — this closes the R3-2 gap the PR body flags as the one behavior change worth a deliberate look, and it's correctly implemented: a returncode == 0 with unproven containment still raises converter_containment_failed (line 2575).
  • Windows production path: Job Object created without BREAKAWAY_OK, assignment verified before resume (line 503), terminate_job() + authoritative job_process_ids() membership check, fails closed (job_membership_unreadable) rather than falling back to an ancestry walk when membership is unreadable (lines 638-659). Sound.
  • POSIX path: pgid pinned via os.waitid(..., WNOWAIT) so the leader isn't reaped (and its pid/pgid isn't recycled) until after the sweep runs on every exit path via close() (lines 586-609); zombie-aware liveness (_posix_group_members excludes state Z); group-wide grace period. Consistent with the documented "best-effort, not a hard boundary" scope for POSIX (setsid() escape is a known, explicitly-documented residual — reasonable to accept given the shipped path is Windows).
  • All prior CodeRabbit/Copilot/Codex findings I spot-checked against their cited commits (POSIX close() no-op, PID-reuse reporting, job-assignment-then-resume race, ERROR_MORE_DATA truncation, zombie liveness, PowerShell WaitForExit unbounded, stale-PID re-signal, orphan-child-of-recycled-PID) match their claimed fixes in the code as it stands now.

Two findings from the codex pass on d930d88 remain open (no reply/fix since)

d930d88 is a main-merge with no changes to either file since 841277a, so codex's last review found these against code that has been sitting unaddressed:

1. Stop-ConverterProcessTree fixed-point race can still miss a late fork — Low, ps1-only, non-ship path
bim-streaming-server/scripts/convert-ifc-to-usdc.ps1:531-606. Each round walks the tree, then kills discovered members in reverse order. A member can fork a new child in the gap between its round's walk and its own kill signal. If that gap lands in the round that becomes the second consecutive zero-discovery round, stableRounds hits 2 and the loop exits without a walk that would have caught it. Real, but narrow, and the function's own docstring (lines 506-510) already states the ps1 tree walk is diagnostic/best-effort — the authoritative boundary on the shipped Windows path is the Job Object in the Python adapter, which doesn't have this race (membership comes from QueryInformationJobObject, not a PID walk).
Fix this →

2. terminate_pid failure is unverified on the Windows launch-failure path — Low, no test coverage of the failure branch
ifc2usdc_powershell_adapter.py:349-356 (terminate_pid) never checks whether OpenProcess/TerminateProcess succeeded. It's the only cleanup used when job assignment fails (lines 504-512) or resume fails (lines 513-520) for a CREATE_SUSPENDED child — after it, the code closes the job handle, does a best-effort 5s reap that swallows any exception/timeout (_reap_after_failed_launch), and raises OSError as if cleanup succeeded. Confirmed by the test double: _FakeWin32ContainmentApi.terminate_pid (test file line ~4010) always "succeeds," so test_windows_launch_kills_the_suspended_child_when_job_assignment_fails never exercises a real termination failure. Impact is a silent resource leak, not a containment escape — the leaked process is permanently suspended and never executes, so it can't touch untrusted input. Still worth closing since this is exactly the kind of unverified-cleanup gap the rest of this PR was written to eliminate.
Fix this →

Verdict

The core SEC-001/L1-COR-004 containment fix is sound and the shipped Windows production path (Job Object, no breakaway, assignment-verified-before-resume, fail-closed on unreadable membership, containment proof on every exit path) does what the PR claims. Both open findings above are low severity — one is scoped-out-of-authority by the code's own docs, the other is a leaked-but-inert suspended process, not a security regression — and neither blocks the SHIP verdict already reached by the tri-adversarial gate and the human approval. Recommend landing a small follow-up (or a quick fixup commit here, if preferred) for #2 before it's forgotten, since it has zero test coverage today.
· branch fix/489a-converter-tree-containment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants