Repository navigation
fix(context-providers): probe provider liveness without Windows console signals - #6354
Conversation
…#6354 Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
…le signals The ownership guard behind rolling issue-fix repository memory sync probed the receipt pid with os.kill(pid, 0). On Windows signal 0 maps to GenerateConsoleCtrlEvent(CTRL_C_EVENT, pid): an exited or unknown pid raises OSError [WinError 87] instead of returning the typed provider_service_process_unavailable result, and a live pid broadcasts Ctrl+C to the caller's console. Reuse the existing Windows-aware loopx.file_lock.process_is_alive probe, matching the shared owner already used by the control-plane effect runtime. Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
Add regression tests for the typed unavailable result, the delegation to the shared non-signaling probe, and a native Windows guard that no process signal is sent; extend the shipped ownership smoke with the exited-provider case and run the new tests in the windows-powershell CI job. Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
b084ca4 to
84876e4
Compare
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent; gpt-6.1-sol; OpenAI; runtime_reported; xhigh
Exact head: 84876e485e1f3ca8c1b55e054cda56e4c4ef0970
动机
Windows 上使用持续运行的资料服务、通过命令同步仓库资料的使用者。
原来检查服务进程是否还活着会发送控制事件,已退出的进程可能引发裸错误或被错判仍存活;现在复用已有安全探针,并把不可用结果交回既有停止规则。
已验证已退出进程返回 typed blocked、服务有效时完成本地资料交付、无效或越界资料不触发写入,以及修正服务回执后恢复同步。
本次没有独立运行 native Windows、真实 OpenViking 账户、真实模型或用户安装的 App;本地资料服务是明示的合成 IO 边界。
native Windows 独立重测和仓库原有 canary 失败仍待各自 owner 处理;这个有界核心探针修复不覆盖独立发行的 operator runtime。
改动思路
删除服务检查中的第二份 OS 探针,复用现有 file_lock owner;回执、停止和重启规则仍由原来的消费者承担,没有新增存储、能力开关或生命周期框架。 当前 PR 修复核心 rolling 同步入口,保留 pinned 和关闭能力的原行为。独立发行的 operator runtime、native Windows 独立资格和既有 canary 失败不被这次批准吞并。 Python 留在稳定 OS/provider 传输边界,未复制新的控制面决策。选择器仍先校验同一份 provider、service、generation、PID 和材料声明,之后才检查活性;同步前后检查与重启后禁止冒称 resume 的规则保留。关闭配置和 pinned 模式不因导入 helper 自动启用持续服务门槛。
具体改动
原规范为 缺陷 #6349,本次读取的版本 6349-body-sha256:a522d2a24b4aa03a749cc9897efceb1420732800b69361f951e15435e4924783;同时核对现有 helper 和 issue-fix README 的服务回执合同。exited_provider 已实现:旧服务进程退出时 unavailable / not_started,写入前停止;non_signaling 已实现为调用既有非控制事件探针,并补充跨宿主 delegation 和 native Windows 用例,独立原生 Windows 运行资格保留为未测;separate_operator 按原问题列出的独立发行边界保留,不从本次核心修复推导该包已经修复。
关键代码讲解
loopx/capabilities/context_providers/service_ownership.py:94的load_context_provider_service_ownership删除私有 os.kill 检查,改调用现有 helper;原 schema、字段闭集、provider、mode、布尔/负 PID 拒绝和隐私投影没有改写。loopx/file_lock.py:657的process_is_alive及:677的 Windows 分支本身完全未修改,也与当前 main 相同;它使用 OpenProcess / WaitForSingleObject 检查进程,及时关闭句柄。旧方法在 Windows 调用控制事件 API 的风险与 CPython 3.11.9 官方实现 相符。这里只证明活性,不认证账户、服务身份或模型采用。loopx/capabilities/issue_fix/repository_memory_provider.py:1252的sync_issue_fix_repository_memory保留同步前阻断、同步后所有权/重启检查和 bounded references。实际 CLI 在模拟 Windows OS 失败 seam 下,修复前返回裸错误,当前返回 typed blocked / not_started,provider 未被调用。它测试真实消费者通路,OS seam 和 provider IO 的替代边界均明示。
对主干的风险
全 4 文件 +113/-12 已读:生产代码只有一处 helper 替换,余下是退出进程回归、shipped smoke 和既有 Windows CI lane 的接入。当前 main 的 file_lock、issue-fix CLI 和同步消费者与修复前 blob 一致,没有用一个正常标题或健康 endpoint 代替消费者核验。
190 个 provider/file-lock/effect/issue-fix 用例通过,3 个平台用例跳过;两个 shipped smokes 通过。修复前 delegation oracle 独立失败,当前通过。11 组完整 API 和 3 组完整 CLI payload 与 base 逐字段相等,没有删字段或归一决策文本;关闭配置同样保持。普通 wheel 复现同一批完整结果。另用真实 CLI 和物理隔离本地 IO 走过已声明材料成功、同目录未声明材料拒绝、新声明材料成功、越界拒绝、死 owner 阻断、修正回执恢复交付,以及 pinned 无服务门槛成功;provider 传输是合成实现,没有动真实账户。
必须保留验证限制:本次 canary 的 5 个直接检查通过,7 个 catalog 检查有 1 个失败,public boundary 通过。examples/issue-fix-validated-memory-writeback-smoke.py 在 head 和同一干净 Git worktree 的原始 b0 基线都失败于第 733 行的同一个子命令步骤;观察到两侧完整错误 payload 逐字相同:repository recovery ref must resolve exactly to repository_revision。这个 fixture 把 HEAD 对应的 revision 与当前 origin/main 的恢复引用混用,严格引用边界拒绝正确;新探针不在这条 pinned outcome 失败路径。仍不能把 gate 记为通过。最初 archive-only 比较在更早的 351 行失败,这份准备证据不用于假装相同基线资格。最小后续是原 owner 保留断言、复核这项失败并恢复完整 gate;本次没有改 smoke、放宽预算或咨询 CI。
我的整体评价
APPROVE 当前有界核心修复。长程效果正向:资料服务退出时停止无效写入,服务事实修正后可继续交付,旧成功不用于掩盖新失败;体验正向:用户拿到既有可行动的 blocked 结果,正常资料同步和关闭能力路径保持。这是机制及受控入口证据,不能把测试、回执或一次本地交付折算为长期模型净收益。相邻收敛删除了第二份 OS 判断;扩大 helper 模块迁移目前没有额外价值,留在原 owner 更易回滚。复用既有状态与原因词汇,没有新 schema 或宽泛生命周期名。上述既有 canary 失败仍是 gate 的真实 hold;这份代码评审不将它豁免,也不认证 native Windows、独立包、CI 或合并准备就绪。
English verdict: APPROVE the bounded current-head repair. Verified source and ordinary wheel behavior improve failure truth and recovery while preserving existing authority. Native Windows/model/domain net utility remain independently unqualified.
Goal And Delivered Outcome
Outcome basis / optional anchor: defect report [Bug]: context-provider ownership probe breaks repository-memory sync on Windows (WinError 87, console Ctrl+C) #6349 (reproduced on native Windows; module behavior unchanged since Guard rolling context provider sync ownership #1956).
Goal/source and gap: the outcome basis implementation (
loopx/capabilities/context_providers/service_ownership.py) probed the receipt pid withos.kill(pid, 0). On Windows that is not an existence probe: signal 0 maps toGenerateConsoleCtrlEvent(CTRL_C_EVENT, pid), so an exited/unknown provider pid escapes the handler asOSError [WinError 87](or is silently reported alive for a still-referenced zombie pid), and a live pid broadcasts Ctrl+C to every process on the caller's console. The typed contract already exists for these cases (status=unavailable,reason_code=provider_service_process_unavailable), and the repository already ships the correct Windows-aware probe inloopx/file_lock.py::process_is_alive.Observable before -> after, with the validation row that proves it: before,
loopx issue-fix repository-memory-sync --executewith a rolling provider receipt whose pid no longer exists returned{"ok": false, "mode": "issue-fix", "error": "[WinError 87] ..."}(exit 1), andexamples/context-provider-service-ownership-smoke.py/ the CLI could trigger a console Ctrl+C; after, the same command returns the typedblockedpacket withprovider_service_ownership.status=unavailable,reason_code=provider_service_process_unavailable,progress_disposition=not_started(rowreal_entrypoint), and the shipped smoke passes (rowintegration).Issue/task and intended base: Closes [Bug]: context-provider ownership probe breaks repository-memory sync on Windows (WinError 87, console Ctrl+C) #6349. Base: upstream
main(branch rebased ontob0b2acffbbefore submission).Author Declaration
AI disclosure: this change was drafted with AI assistance at my request, under my direction and review.
Implemented against
loopx/file_lock.py::process_is_alive("Probe a process without sending signals or console control events").unavailable/provider_service_process_unavailableinstead of raising (#6349 expected)loopx/capabilities/context_providers/service_ownership.pytests/test_context_provider_service_ownership.py::test_exited_provider_pid_is_typed_unavailableloopx/file_lock.py::process_is_alive)tests/test_context_provider_service_ownership.py::test_ownership_probe_delegates_to_the_shared_non_signaling_probe,::test_native_windows_ownership_probe_sends_no_process_signal(runs inwindows-powershellCI)examples/context-provider-service-ownership-smoke.pypython examples/context-provider-service-ownership-smoke.pypackages/loopx-codex-provider-routingoperator runtimepackages/loopx-codex-provider-routing/src/.../operator_runtime.pyprocess_is_aliveand its existing importer (loopx/control_plane/effect_runtime.py) before reusing it. Left out: the separately distributed operator-runtime copy of the probe (see criteria table) and Linux/macOS hosts (POSIX behavior is unchanged by construction and covered by the same tests).Scope And Continuation
out_of_scopeabove.loopx/file_lock.py::process_is_alive, already imported byloopx/control_plane/effect_runtime.py), so the next liveness-probe change has one place to land. A broader relocation of that helper out offile_lock.pywas considered and deferred: it has two existing importers and a move would widen this diff without changing behavior for the linked defect.Validation
84876e485unitpassedpython -m pytest -q tests/test_context_provider_service_ownership.py: on unmodified base71304fcbd3 failed (OSError: [WinError 87]fromos.kill; shared probe never consulted;os.killcalled on Windows), on head 3 passed.integrationpassedpython examples/context-provider-service-ownership-smoke.py->context-provider-service-ownership-smoke: ok; the same smoke on unmodified base aborts in the exited-provider case.python -m pytest -q tests/test_file_lock.py tests/control_plane/test_effect_runtime_integration.py tests/test_context_provider_service_ownership.py-> 97 passed, 3 skipped.real_entrypointpassedloopx issue-fix repository-memory-sync --execute --format jsonagainst a throwaway repo and a receipt with a stale pid: base{"ok": false, "mode": "issue-fix", "error": "[WinError 87] ..."}; head typedblockedpacket withreason_code=provider_service_process_unavailableandprogress_disposition=not_started.python examples/issue-fix-repository-memory-smoke.pyon base died with STATUS_CONTROL_C_EXIT (0xC000013A, the probe's console Ctrl+C); on head it printsissue-fix-repository-memory-smoke: ok.regression_paritypassedtests/capabilities/test_issue_fix_*.py: 93 passed on both unmodified base and head.python -m pytest -q tests/capabilities tests/control_plane ...could not be completed on this host in reasonable time; the touched import chain is covered by the rows above.staticpassedruff checkon the changed Python files;loopx canary premerge --from-git-diff --git-diff-base upstream/maindirect checks passed (diff hygiene, changed-filepy_compile, module-ceiling ratchet, public boundary).manualpassedcontext-provider-service-ownership-smoke.pypassed,lark-projection-source-reconcile-smoke.pypassed;semantic-vocabulary-drift-smoke.py,issue-fix-outcome-projection-smoke.py,issue-fix-validated-memory-writeback-smoke.pyandcli-output-budget-regression-smoke.pyfail identically on the unmodified base (missing npm dev dependencies; pre-existing smoke assertions; Windows temp-dirPermissionError), so they are environment/baseline failures, not regressions from this diff.OpenProcess/WaitForSingleObjectunderos.name == "nt"); the delegation test simulates that branch on any host, so the Linux CI shard also fails if the module reintroduces a localos.killprobe. Untested locally: POSIX hosts (unchanged code path), the separately distributed operator runtime.Frontend / Visual Evidence
Type of Change
LoopX Area
Technical Direction
Shared-authority RFC fixture impact
Boundary Checklist
.loopx/,.codex/goals/, and liveACTIVE_GOAL_STATE.md).none.Signed-off-bytrailer (git commit -s).