Skip to content

fix(context-providers): probe provider liveness without Windows console signals - #6354

Merged
huangruiteng merged 2 commits into
loopx-project:mainfrom
JasonBuildAI:codex/context-provider-windows-probe-q9w2
Oct 13, 2026
Merged

huangruiteng merged 2 commits into
loopx-project:mainfrom
JasonBuildAI:codex/context-provider-windows-probe-q9w2

Conversation

@JasonBuildAI

@JasonBuildAI JasonBuildAI commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

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 with os.kill(pid, 0). On Windows that is not an existence probe: signal 0 maps to GenerateConsoleCtrlEvent(CTRL_C_EVENT, pid), so an exited/unknown provider pid escapes the handler as OSError [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 in loopx/file_lock.py::process_is_alive.

  • Observable before -> after, with the validation row that proves it: before, loopx issue-fix repository-memory-sync --execute with a rolling provider receipt whose pid no longer exists returned {"ok": false, "mode": "issue-fix", "error": "[WinError 87] ..."} (exit 1), and examples/context-provider-service-ownership-smoke.py / the CLI could trigger a console Ctrl+C; after, the same command returns the typed blocked packet with provider_service_ownership.status=unavailable, reason_code=provider_service_process_unavailable, progress_disposition=not_started (row real_entrypoint), and the shipped smoke passes (row integration).

  • 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 onto b0b2acffb before submission).

Author Declaration

AI disclosure: this change was drafted with AI assistance at my request, under my direction and review.

Implemented against

Criterion (spec clause) Disposition Symbol / path Test or command
A receipt whose provider pid has exited returns unavailable / provider_service_process_unavailable instead of raising (#6349 expected) implemented loopx/capabilities/context_providers/service_ownership.py tests/test_context_provider_service_ownership.py::test_exited_provider_pid_is_typed_unavailable
The check must not send signals or console control events (shared-owner contract) implemented same (delegates to loopx/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 in windows-powershell CI)
The shipped ownership smoke keeps proving the receipt contract, now including the exited-provider path implemented examples/context-provider-service-ownership-smoke.py python examples/context-provider-service-ownership-smoke.py
The same POSIX-only probe in the separately distributed packages/loopx-codex-provider-routing operator runtime out_of_scope packages/loopx-codex-provider-routing/src/.../operator_runtime.py named in #6349; that package cannot import the loopx helper and needs its own decision
  • Self-check before submission: reproduced the defect at the function boundary, through the real CLI, and through both shipped smokes on an unmodified base checkout; verified the typed packet shape against the sibling receipt-unavailable case before touching code; ran the new tests against the unmodified base (3 failed) and head (3 passed); compared the failing canary catalog checks against the same unmodified base; read process_is_alive and 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

  • Completed scope and remaining work: the first-party ownership guard now uses the shared Windows-aware probe; the dead-provider path and the console-signal side effect are covered by unit tests, the shipped smoke and the Windows CI lane. Complete within this scope; the only remaining instance of the pattern is the separately distributed operator runtime named as out_of_scope above.
  • Adjacent future-facing pass: applied the bounded part - the duplicated local probe was deleted in favor of the existing shared owner (loopx/file_lock.py::process_is_alive, already imported by loopx/control_plane/effect_runtime.py), so the next liveness-probe change has one place to land. A broader relocation of that helper out of file_lock.py was considered and deferred: it has two existing importers and a move would widen this diff without changing behavior for the linked defect.
  • Slice boundary / successor: N/A - the accepted defect in [Bug]: context-provider ownership probe breaks repository-memory sync on Windows (WinError 87, console Ctrl+C) #6349 for this module is fixed end to end.

Validation

  • Tested revision: 84876e485
  • Run state: finished
  • Input classes: synthetic
Check kind Result Public-safe evidence / limitation
unit passed python -m pytest -q tests/test_context_provider_service_ownership.py: on unmodified base 71304fcbd 3 failed (OSError: [WinError 87] from os.kill; shared probe never consulted; os.kill called on Windows), on head 3 passed.
integration passed python 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_entrypoint passed loopx issue-fix repository-memory-sync --execute --format json against a throwaway repo and a receipt with a stale pid: base {"ok": false, "mode": "issue-fix", "error": "[WinError 87] ..."}; head typed blocked packet with reason_code=provider_service_process_unavailable and progress_disposition=not_started. python examples/issue-fix-repository-memory-smoke.py on base died with STATUS_CONTROL_C_EXIT (0xC000013A, the probe's console Ctrl+C); on head it prints issue-fix-repository-memory-smoke: ok.
regression_parity passed tests/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.
static passed ruff check on the changed Python files; loopx canary premerge --from-git-diff --git-diff-base upstream/main direct checks passed (diff hygiene, changed-file py_compile, module-ceiling ratchet, public boundary).
manual passed Canary catalog: context-provider-service-ownership-smoke.py passed, lark-projection-source-reconcile-smoke.py passed; semantic-vocabulary-drift-smoke.py, issue-fix-outcome-projection-smoke.py, issue-fix-validated-memory-writeback-smoke.py and cli-output-budget-regression-smoke.py fail identically on the unmodified base (missing npm dev dependencies; pre-existing smoke assertions; Windows temp-dir PermissionError), so they are environment/baseline failures, not regressions from this diff.
  • Coverage and gaps: the Windows-only test exercises the real probe branch (OpenProcess/WaitForSingleObject under os.name == "nt"); the delegation test simulates that branch on any host, so the Linux CI shard also fails if the module reintroduces a local os.kill probe. Untested locally: POSIX hosts (unchanged code path), the separately distributed operator runtime.

Frontend / Visual Evidence

  • UI impact: none
  • Before: N/A
  • After: N/A
  • States and viewports shown: N/A
  • Source data: none
  • Attention review: N/A - no user-visible surface changes.
  • Craft review: N/A - no UI change.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Refactoring (no functional changes)
  • Documentation update
  • Test update

LoopX Area

  • Control plane (goals, todos, quota, scheduler, registry, runtime)
  • Benchmark boundary (adapters, runners, verifiers, scoring, evidence)
  • Capability or extension (providers, adapters, skills)
  • Public docs or presentation surface (README, protocols, dashboard)
  • Build, packaging, installer, or CI
  • Host or runtime integration

Technical Direction

  • Direction / acceptance reference, when applicable: N/A - self-contained defect repair against the existing ownership contract.

Shared-authority RFC fixture impact

  • Production-scale fixture schema: N/A
  • Semantic dimensions changed, or reviewed no-impact rationale: N/A - the semantic coinage probe reports no new vocabulary carriers; the change removes a private helper and reuses existing reason codes.
  • Provider conformance arms run: N/A
  • Read-only legacy/file/PostgreSQL three-arm rehearsal (required for promotion, runtime-routing, or compatibility-projection changes): N/A

Boundary Checklist

  • Neither the diff nor this PR body/comments/attachments disclose private state, credentials, raw traces or verifier output, internal links, or local machine paths (including .loopx/, .codex/goals/, and live ACTIVE_GOAL_STATE.md).
  • I did not duplicate maintainer-owned benchmark work unless a maintainer split out a public issue for it.
  • I kept the change scoped to the linked issue/task.
  • I completed the visual evidence section for UI changes, or marked UI impact none.
  • Every commit includes a DCO Signed-off-by trailer (git commit -s).

JasonBuildAI added a commit to JasonBuildAI/loopx that referenced this pull request Oct 13, 2026
…#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>
@JasonBuildAI
JasonBuildAI force-pushed the codex/context-provider-windows-probe-q9w2 branch from b084ca4 to 84876e4 Compare October 13, 2026 04:24

@loopx-agent loopx-agent 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.

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.

@huangruiteng
huangruiteng merged commit b644a97 into loopx-project:main Oct 13, 2026
29 of 35 checks passed
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.

[Bug]: context-provider ownership probe breaks repository-memory sync on Windows (WinError 87, console Ctrl+C)

3 participants