Skip to content

fix(update): prevent Windows console self-update from uninstalling itself - #6805

Open
BigDataDZ wants to merge 3 commits into
mainfrom
codex/windows-console-update-guard-20261018
Open

BigDataDZ wants to merge 3 commits into
mainfrom
codex/windows-console-update-guard-20261018

Conversation

@BigDataDZ

@BigDataDZ BigDataDZ commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Windows pip/pipx self-update launched through its own console executable can encounter WinError 32 after pip has removed package files (#6804). Refuse this entrypoint before installer or prompt reconciliation, and print an owning-interpreter python -m loopx.entrypoint retry preserving the original arguments.

Recognize the actual distlib ZIP launcher, including a linked pipx suffix alias when it is the same physical file as the owning environment's loopx.exe. Identical bytes in a separate unrelated executable do not trigger this classification. Recheck the entrypoint when executing a saved plan. Read-only check/plan, module invocation, other platforms and external installation owners retain their behavior. Reuse the existing host-aware interpreter renderer and document the PowerShell recovery route.

Validation at head 10d31911a3ceae51590df1a5e1ab07d78c926861, rebased on main dc9fdbb5186618ec2e797a9d7545e2fe524fee56:

  • Native update/runtime, download and installation-owner tests: 60 passed, 16 POSIX skips. Linked-alias regression first failed for both pip and pipx before the fix. Independent review found no blockers and independently passed 10 focused guard/quoting cases.
  • Ordinary full wheel, no source overlays: SHA-256 0bd5dab873336472251d7060cae0ae3631fd37ae26d509997371a5eb3d6be2b8; 1,801 installed package files match the archive before and after recovery.
  • Real Windows PE entrypoints in fresh noneditable pip and genuine pipx --backend=pip --suffix=-preview installations refuse before effects. Complete package, launcher, synthetic project/runtime/home files and pipx metadata remain unchanged.
  • Execute each exact printed PowerShell retry. Actual pip reinstall and actual pipx upgrade loopx-preview replace the candidate package; install, authority upgrade, workflow skills, slash commands, doctor and extension doctor all return zero. Fresh installed CLI processes preserve the SQLite-backed task/hard lease, reject an invalid artifact, complete the valid one and replay the same receipt without mutation.
  • Changed-source Ruff, whitespace checks, advisory vocabulary scan and full-tree semantic vocabulary smoke pass. The advisory is not semantic-equivalence proof. Existing source vocabulary scan limitations remain visible.

These are isolated offline same-version candidate-wheel replacements, with forced reinstall in disposable process configuration. They do not qualify a cross-version upgrade, uv-backed pipx, copied aliases, arbitrary installer-failure rollback, optional extension providers or automatic host/session continuation. Protected existing user-state hashes remain unchanged. Earlier source-pinned evidence and failed attempts stay in the discussion; they do not certify the current head.

Scope: existing Python installation adapter and CLI recovery, with no new product capability or authority grant. The related refactor extends the existing renderer instead of introducing a parallel platform decision owner. Hosted CI and maintainer exact-head re-review remain separate requirements; runtime changes are left for the maintainer to merge.

Fixes #6804.

@BigDataDZ

Copy link
Copy Markdown
Contributor Author

Follow-through at unchanged head b6d901e26d4ceea34c24721827f19df87523c86f:

These are independently reviewable follow-ups, not additions to this runtime safeguard or claims that its missing cross-platform CI has passed. Runtime merge remains with the maintainer.

@BigDataDZ
BigDataDZ force-pushed the codex/windows-console-update-guard-20261018 branch from b6d901e to 53c6e8a Compare October 18, 2026 10:07

@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 · reasoning_effort=max

Exact head: 53c6e8a9ccb006e4895138171597401ed7087d8a

动机

[P2] 原生 Windows 安装态资格尚未独立验证。 当前没有复现新实现错误;REQUEST_CHANGES 针对 #6804 明确要求的原生控制台、包/任务保全与成功模块恢复证据缺口。在可用的原生 Windows 隔离环境,以当前 exact head 的普通构建安装验证真实 loopx.exe 拒绝前无安装/提示协调效果、包可导入和原合成 Todo/lease 不变,再验证输出的模块命令可通过原安装 owner 恢复并读回结果;保留完整命令、产物身份和失败。无需更改源码或扩大功能才能补齐这项证据。

通过 Windows 的 loopx.exe 升级 pip/pipx 安装的用户,需要在原安装被拆掉之前得到安全的拒绝和可继续使用的升级入口。

具体场景是用户运行 update apply:旧行为会在外层 launcher 仍存活时启动安装器,可能在卸载一部分文件后遇到占用;本修改拟先拒绝,并返回同一 Python 环境的模块命令,保留原参数。

本轮源码及普通非 editable wheel 的真实 ZIP loader/CLI 模型都在受限调用上拒绝,且未进入提示协调或安装器;check、plan、模块入口和其他入口仍走原路。原生 Windows 安装态的成功恢复仍缺少本轮独立证据。

本 PR 限于该 launcher 锁冲突的提前拒绝及重试提示,不承诺任意 pip 失败都原子回滚,也不增加 App/Lark 更新能力或授予升级权限。

改动思路

最小合理边界是既有 Python 安装 owner:在计划构建和执行保存计划两处检查实际 ZIP loader,再复用唯一的解释器命令 renderer;不需要新协议、全局配置或另一个 TypeScript/Python 决策源。

保留本次有界源码修复及 renderer 合并;完整批准还需把 #6804 的原生控制台、安装保全和模块恢复验收接到当前普通构建产物。

调用顺序很关键:当前 launcher 拒绝先于 prompt reconciliation 和 pip,执行保存计划也重新看当前入口。另一方面,check/plan 与可用模块入口必须保留,这样才有完成更新的最短合法路径。源码和普通安装包的模型均验证了这两个方向;真实 Windows 的成功续接仍未观测。

具体改动

完整 a0fb547af9aac4e4d36f59fb05cb64eb56fd750f → 53c6e8a9ccb006e4895138171597401ed7087d8a:six paths: two Python owner/runtime modules, two existing tests, the native Windows workflow list and the installation guide,+167/-4。

  • _windows_console_update_refusal:Existing Python owner gains actual loader check;win32 + zipimporter + archive basename loopx.exe + pip/pipx driver。结果由 build_update_plan and saved-plan executor 消费。
  • _execute_python_distribution_update:Rechecks current invocation before subprocess.run;Any still-to-execute pip/pipx plan; committed replacement is not inferred。结果由 existing package transaction pipeline 消费。
  • interpreter_invocation:Optional argument list uses existing host quoting;PowerShell call operator/single-quote literal or POSIX shlex.join。结果由 refusal retry, existing doctor/rollback callers 消费。
  • test_real_windows_console_refuses_before_starting_installer:New real distlib launcher test is conditional on Windows;A real PE launcher must refuse before sentinel install。结果由 native Windows workflow 消费。

规范 #6804,spec_revision=2026-10-18T08:53:12Z;Acceptance.native_console not_met:On native Windows prove refusal or replacement through the real console entry;Acceptance.original_task_lease not_met:Preserve an installed/importable CLI and the original synthetic task and lease;Acceptance.module_recovery not_met:Successful module-entry recovery; preserve incomplete-update evidence and owner behavior。

新增测试还覆盖 pip/pipx、原参数中引号/美元符号、check/plan 不变与保存计划再检查;Windows-only 用例生成真实 distlib launcher,PowerShell 用例验证特殊字符参数往返,并加入原 windows-powershell job。文档解释先用打印出的拥有者解释器运行模块入口,已损坏的包需先由该 owner 的 pip 修复;这没有自动授予新升级权限。

对主干的风险

Source and normal installed wheel each pass nine independent public-entrypoint models; four covered cases refuse before both effect boundaries, read-only cases retain their plan, unrelated/normal-module cases reach the bounded sentinel. Actual POSIX shell quoting preserves apostrophe, dollar, Unicode, empty and quote arguments.

54 Python passed, 6 conditional skips; 58 CLI/owner tests passed; changed Ruff and semantic advisory passed. Standard canary: 6 direct passed, 14/15 selected passed, install-local-smoke timed out at 120s. Same unchanged 120s runner later passed base39.562s/head41.977s; first direct base run passed with first install168.90s, preserving initial variability without claiming its cause repaired. Managed quality receipt missing.

普通 dashboard build 和 wheel 构建已完成,两处改动生产文件与 wheel 同字节,隔离 -I 运行确认来自 site-packages,9 项入口模型全部符合独立预期。模型用真实 Python ZIP loader 和真实 CLI,但 platform 是注入值,安装器/提示协调只写哨兵;未执行 PE launcher、真实 pip 更新或原生状态恢复,不能把它们标记 verified。真实 POSIX shell 的特殊参数往返和旧无参 renderer 字节兼容已过,PowerShell 仍未执行。首次缺少本地 vite bin link,经正常 npm ci 修复再普通构建;初次平台模拟在 sysconfig 初始化前切换导致导入错误,预加载真实 host CLI 后模型通过,原失败保留。这些是测试环境/脚手架修正,不能归因于产品或当作原生 Windows 证据。

Native Windows, PowerShell, real pip/pipx replacement and original native task/lease preservation remain unverified independently; source/ZIP model and Mac package evidence do not close #6804. Initial installer timeout variability retained, no CI fetched/polled/dispatched, no user installation modified.

相关方案 #6789 仍在同一安装 owner 上组合 CLI/macOS App 与进度读回,没有实现本次命名 launcher 的 Windows 拒绝;6806/6807/6816 在共享 Windows job 添加不同测试。当前 main 对本批改动路径无同义修复。读取 normalized key_files 后按实际 path 字段核对了关系,未把元数据对象误当路径或借别的 PR 的结论。没有查询、轮询、等待或触发 CI。

我的整体评价

最小合理边界是既有 Python 安装 owner:在计划构建和执行保存计划两处检查实际 ZIP loader,再复用唯一的解释器命令 renderer;不需要新协议、全局配置或另一个 TypeScript/Python 决策源。

保留本次有界源码修复及 renderer 合并;完整批准还需把 #6804 的原生控制台、安装保全和模块恢复验收接到当前普通构建产物。

结构和范围合理,renderer 复用是已应用的有界可维护性改进。但 problem_context=not_yet_proven,long_horizon/user_experience=not_yet_proven:模型拒绝尚不能证明用户在真实 Windows 中保全安装并完成下一次更新。请补原 issue 原生验收的可核证据后再给当前 head 的完整批准。无需为了本意见引入 handoff 框架、修改断言、提高超时或扩大改动。

当前投递的恢复设计经验只用于提醒检查首个有用结果与真实恢复,不据此添加无关 RFC 门,也不以本轮测试/评审数量宣称长期收益。

English verdict: REQUEST_CHANGES at 53c6e8a9ccb006e4895138171597401ed7087d8a. The bounded owner/loader design and nine source/ordinary-wheel ZIP-CLI models are sound, but this is not native Windows execution. The issue explicitly requires real-console refusal, installed package/task/lease preservation and successful module recovery; that current-head independent qualification remains missing. Keep the initial120s installer timeout and separate missing quality receipt visible.

@BigDataDZ

Copy link
Copy Markdown
Contributor Author

Native Windows ordinary-wheel qualification for unchanged head 53c6e8a9ccb006e4895138171597401ed7087d8a (2026-10-19).

This supplements the requested native console / installation preservation / module recovery evidence. No tracked source, installed package source, loader or installer was patched or mocked.

Artifact: normal setuptools wheel loopx-2.0.0a1-py3-none-any.whl, SHA-256 c962418a351777f3f18dce68c764dd9e559d10c3e0c16e6063833ecd1a1546f9. After rebuilding the qualified Chat assets, ordinary uv build --wheel succeeded. A fresh noneditable venv installed this complete wheel; all 1,795 package files matched the archive. Isolated python -I confirmed imports from that venv's site-packages before and after recovery.

Native results:

  1. The installed real PE loopx.exe, from a path containing spaces and Chinese characters, ran --registry <synthetic-registry> --runtime-root <synthetic-runtime> --format json update apply --timeout-seconds 120. It returned exit 1, ok=false, execution=null, changes_applied=false and an owning-interpreter PowerShell module retry.
  2. The 1,795 package files and PE launcher had identical hashes before/after refusal. Existing synthetic project/runtime files were identical; no prompt materials appeared. Hash accounting excludes Python bytecode caches and transient lock files.
  3. Executing the exact printed retry in a fresh pwsh -NoProfile -NonInteractive process completed actual pip uninstall/reinstall, followed by authority upgrade, workflow-skills installation, slash-command installation, doctor and enabled-extension doctor: all six return codes 0. Both Successfully uninstalled loopx-2.0.0a1 and Successfully installed loopx-2.0.0a1 were observed. The isolated fixture has zero enabled extension providers; that last stage does not qualify external provider behavior.
  4. The real installed CLI had created the synthetic Goal on SQLite authority with a hard lease. Fresh post-replacement console processes read the original open Todo and unchanged lease owner/version. Invalid artifact completion was rejected; the valid UTF-8 artifact completed with a receipt; replay returned the same receipt with changed=false.
  5. The three separately protected user-state files retained their pre-run hashes. Runtime/installer writes were confined to the disposable installation, synthetic project/runtime and fixture home.

Reproduction command shapes (only private physical roots are replaced with placeholders):

uv build --wheel --index-url https://pypi.tuna.tsinghua.edu.cn/simple --out-dir <owned-wheel-directory>
& <isolated-python> -m pip install --no-index --no-deps <candidate-wheel>
& <isolated-loopx.exe> --registry <synthetic-registry> --runtime-root <synthetic-runtime> --format json update apply --timeout-seconds 120
# Preserve the returned command literally, then execute it in a fresh PowerShell process.

During recovery only, the disposable process inherited PIP_NO_INDEX=1, PIP_FIND_LINKS=<owned-wheel-directory> and PIP_FORCE_REINSTALL=1. This selects a real same-version candidate-wheel replacement, avoiding publication of a synthetic release. It proves the module-entry package replacement/recovery path, not a cross-version upgrade or a release. The earlier published 1.4.0-overlay → 1.4.2 experiment remains separate historical evidence. Real pipx replacement and automatic host/session continuation are not qualified by this run.

Build failures remain distinguished: the first dependency-fetch attempt was stopped; the next ordinary wheel build rejected stale cached Chat assets; normal npm run build:chat and a subsequent ordinary wheel build passed without bypassing package validation. The earlier reviewer-recorded installer timeout remains historical and is not relabeled as fixed.

Independent native reproduction also passed in a second disposable noneditable installation of the same hashed wheel, with its own SQLite-backed synthetic Goal. The independent operator audited the harness and added MZ/x64-PE signature, fresh python -I import, native console version and post-replacement archive-parity checks. It independently observed the same refusal/hash preservation, real uninstall/reinstall, six successful stages, task/lease preservation, validation rejection, valid completion and unchanged receipt replay. It found no additional qualification defect; the same-version/offline/pip-only boundaries above remain.

This is an author qualification response, not a merge or approval. The current PR remains subject to maintainer re-review and current merge checks.

Signed-off-by: BigDataDZ <76271875+BigDataDZ@users.noreply.github.com>
Signed-off-by: BigDataDZ <76271875+BigDataDZ@users.noreply.github.com>
@BigDataDZ

Copy link
Copy Markdown
Contributor Author

Additional native Windows qualification for unchanged #6805 head 53c6e8a9ccb006e4895138171597401ed7087d8a: real pipx ownership and recovery, using pipx 1.18.4 with its explicit pip backend and the default unsuffixed loopx application/environment.

The complete ordinary wheel is the same artifact already identified above: SHA-256 c962418a351777f3f18dce68c764dd9e559d10c3e0c16e6063833ecd1a1546f9. A separately installed pipx manager, application home, exposed-bin directory, fixture home and synthetic Goal were all disposable. No pipx metadata or LoopX package sources were fabricated or patched.

  • Installed by package name with pipx install loopx --backend=pip --pip-args=--pre, resolving only from the owned candidate wheelhouse. Genuine metadata recorded backend pip, environment loopx and package spec loopx.
  • The exposed native PE loopx.exe refused apply with exit 1, execution=null, changes_applied=false. All 1,795 package files, launcher, substantive project/runtime files and original pipx metadata hash remained unchanged; no prompt materials appeared. Bytecode caches and transient lock files are excluded from hash accounting.
  • The exact printed PowerShell module retry selected python_pipx / environment loopx and invoked real pipx upgrade loopx. Pipx's own log records actual pip uninstall and reinstall of 2.0.0a1; all six update/readback stages returned zero. Original environment, package-name spec, backend and pip arguments were preserved.
  • Fresh isolated imports and the exposed native console worked afterward; all 1,795 installed package files still matched the wheel. The original SQLite-backed Todo/hard lease survived, invalid artifact completion was rejected, valid UTF-8 completion succeeded, and receipt replay returned changed=false. Three protected user-state files retained their pre-run hashes.

This remains same-version offline replacement, enabled by PIP_NO_INDEX=1, PIP_FIND_LINKS=<owned-wheelhouse> and PIP_FORCE_REINSTALL=1 in the disposable processes. It does not establish a cross-version upgrade, pipx's uv backend, custom application aliases or automatic session continuation. Enabled-extension doctor passed on the empty enabled-provider fixture, without external-provider qualification.

Two tooling setup failures are retained separately: pipx rejected a split --pip-args --pre argument before application installation, and its automatic uv selection was stopped before application installation. A fresh owned home and supported explicit pip-backend selection then completed the qualification above. These setup corrections are not LoopX runtime fixes.

The earlier pip-only run remains separate evidence; this supplement closes the real default-name pipx/pip-backend execution gap.

Remaining safety blocker at this head: a separate real pipx install --suffix=-preview exposes loopx-preview.exe as a same-file symlink to the owned loopx.exe, while the native ZIP loader retains the suffixed archive name. A bounded native-loader plan/execution probe showed that the current exact-name guard permits this alias to reach pipx upgrade loopx-preview. The installer call was intercepted by a sentinel before starting any child; no alias uninstall was performed. I am updating the PR to identify the linked owned file. The passing default-name qualification above must not be read as coverage of this alias or as approval of the old head.

Signed-off-by: BigDataDZ <76271875+BigDataDZ@users.noreply.github.com>
@BigDataDZ
BigDataDZ force-pushed the codex/windows-console-update-guard-20261018 branch from 53c6e8a to 10d3191 Compare October 19, 2026 02:08
@BigDataDZ

Copy link
Copy Markdown
Contributor Author

Author follow-through at 10d31911a3ceae51590df1a5e1ab07d78c926861, rebased onto main dc9fdbb5186618ec2e797a9d7545e2fe524fee56.

The linked suffix bypass recorded above is fixed: compare physical file identity with the owning interpreter's loopx.exe when the active distlib archive has an alias name. Both pip/pipx alias regressions failed before this fix, then passed. A separate executable with identical bytes and a missing owner executable retain their previous classification. Saved-plan execution also checks before effects.

New-head ordinary full wheel: loopx-2.0.0a1-py3-none-any.whl, SHA-256 0bd5dab873336472251d7060cae0ae3631fd37ae26d509997371a5eb3d6be2b8. No source overlays. 1,801 installed package files match the archive before and after actual package replacement.

Two independent disposable native Windows installations passed:

  1. Noneditable pip installation in a Unicode/space path, real PE loopx.exe.
  2. Genuine pipx 1.18.4 installation by package name with --backend=pip --suffix=-preview --pip-args=--pre, real exposed PE alias linked to the environment-owned executable.

Both console invocations refuse with exit 1, no execution and no applied changes. Full package/launcher and synthetic project/runtime/home hashes remain unchanged; pipx metadata is unchanged. Execute the exact printed PowerShell module retry: real pip replacement and real pipx upgrade loopx-preview succeed. Install, authority upgrade, workflow skills, slash commands, doctor and extension doctor each return zero. Fresh isolated imports and real PE version readback succeed; pipx retains its package-name spec, pip backend, suffix and owning environment.

Fresh installed CLI processes retain the original SQLite-backed open task and hard-lease owner/version. Invalid artifact completion is rejected; a valid UTF-8 artifact completes with a receipt; replay returns the same receipt with changed=false. Three protected existing user-state files retain their hashes.

Source validation: 60 passed / 16 POSIX skips, changed-source Ruff, whitespace checks, advisory semantic scan and full-tree vocabulary smoke pass. Independent code review found no blockers and passed 10 focused cases. The semantic scan's existing coverage limits remain; it is not a semantic-equivalence claim.

Limits: isolated offline same-version forced replacement, not cross-version upgrade, uv-backed pipx, copied aliases, arbitrary installer-failure rollback, optional extension-provider qualification or automatic host/session continuation. Earlier failed build/setup attempts remain retained with their original source/artifact identities. A summary-only field-name error in the new pipx harness was corrected after preserving its error; no native qualification stage failed.

The previous source-pinned comments remain historical. This is author qualification, not maintainer approval. Hosted CI and exact-head maintainer re-review remain pending; no runtime self-merge was performed.

@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 · reasoning_effort=max

Exact head: 10d31911a3ceae51590df1a5e1ab07d78c926861

动机

[P2] 当前原生 Windows 安装态资格仍未独立闭环。 没有复现新的源码错误;本结论保留 #6804 明确要求的真实控制台、包及任务保全、成功模块恢复这项证据门。

Windows 上通过 pip 或 pipx 安装 LoopX 的用户,在运行自身控制台程序发起升级时会遇到这个问题。

例如正在运行的 loopx.exe 被安装器替换时仍占用文件,旧行为可能先卸载包再失败,导致下一次命令不可用;新方案先拒绝,并给出同一解释器的模块入口继续更新。

本轮独立源码模型确认普通名称与同文件别名都在安装器之前拒绝,check/plan、模块入口、非 Windows 和无关副本保留原路径;真实 Windows 包替换与任务恢复仍只有公开资格记录,尚未独立闭环。

本 PR 限于这个启动器锁冲突与拥有者解释器重试,不承诺任意安装失败回滚,也不新增升级权限。

改动思路

保留既有 Python 安装 owner,在构建计划和执行保存计划两处检查实际 ZIP loader,并复用原解释器 renderer;物理文件身份属于文件系统适配,适合这个现有边界。

本 PR 的源码边界合理,当前批准缺口是把真实控制台拒绝、安装及任务保全、成功模块恢复的原生证据连接到当前普通构建产物。

不修复会保留已复现的部分卸载风险;仅用 argv 或名称会遗漏 distlib 别名,同文件检查是合理的有界补齐。自动 handoff 需要另一个生命周期/结果返还设计,原 issue 已接受提前拒绝和模块重试,本轮不要求扩大实现。额外一次模块命令对应实际文件占用边界,输入和路径由既有上下文生成,无需再让用户重填。

具体改动

完整 dc9fdbb5186618ec2e797a9d7545e2fe524fee56 → 10d31911a3ceae51590df1a5e1ab07d78c926861 六路径 +207/-4:既有生产 owner62行新增、125行测试、安装指南19行及 Windows workflow 选择1行。上次评审 53c6e8a9ccb006e4895138171597401ed7087d8a 到当前提交新增同文件别名识别并变更基线,已重审完整 PR,没有继承上次结论。

关键代码讲解

  • _windows_console_update_refusal:从实际 main ZIP loader、win32 与既有 pip/pipx driver 判断。别名用 samefile 比对拥有者 loopx.exe;相同字节的独立文件不等于同一文件。返回既有拒绝字段和保留参数的拥有者模块命令。
  • _execute_python_distribution_update:保存计划执行时重查当前入口,然后才可能进入原安装器与后续验证;这覆盖了计划先在安全入口生成、后来由受限控制台执行的情况。
  • interpreter_invocation:扩展原 renderer 接收参数,同一处承担 PowerShell 字面量和 POSIX quoting。既有无参数调用保留,新增路由、引号、美元符号及 Unicode 参数模型通过。

规范为 https://github.com/loopx-project/loopx/issues/6804,spec_revision=`2026-10-18T08:53:12Z`。Acceptance.native_console、Acceptance.original_task_lease、Acceptance.module_recovery 均保留 not_met 的独立验收状态;这表示本轮未独立核证对应结果,不否认作者公开记录已经报告通过。

对主干的风险

独立同脚本 SHA ac77623ba585ee4d73edf2b2009d41eae4cb6448f1ed352f2538323d5909016c 在 base/head 各运行12类公开计划/执行入口场景。base 的普通及同文件别名共4类进入安装哨兵而失败,head12类均符合预期;无关副本、缺失 owner、模块、非 Windows 和 check/plan 保留其边界。真实 ZIP loader 与物理 hardlink 已执行,平台值注入且安装器被拦截,不能把它当成 PE、原生 PowerShell 或真实 pip/pipx 替换。

相关测试70 passed、6项原生 Windows/PowerShell 条件 skip;Ruff、差异检查通过。标准 canary 六项 direct 和14/15 selected通过,install-local-smoke在原120秒限制超时,原观察保留,未提高阈值或宣称原因已修复。随后在不可变 base 用相同 canary wrapper、相同120秒限制复现完全相同的空输出 timeout;smoke 与安装脚本未改,POSIX 上新 Windows guard 不激活,归于 pre-existing unrelated,仍保留失败和原有波动,不声称已修复;完整托管 CI 没有查询、轮询、等待或触发。缺少 exact-scope change-quality receipt 仍是独立合并资格限制。

已完整读取作者新 head 的 pip 与带后缀 genuine pipx 原生记录:普通 wheel SHA256为 0bd5dab873336472251d7060cae0ae3631fd37ae26d509997371a5eb3d6be2b8,报告1801包文件一致、真实替换、六阶段成功和合成任务/lease/receipt保全。这是有价值的资格回应;本轮可独立取得的仍是源码与公开文字总结,未取得并核对可重放原生脚本、该普通构建产物和对应结构化读回,因此没有把这些观察提升为独立 verified。最小补充是:提供可独立核对的当前提交原生资格路径:普通 wheel 的不可变身份、可重放的隔离脚本和公开安全结果,覆盖真实 PE 控制台拒绝、包及合成 task/lease 保全、执行打印的模块重试及新进程读回。

语义与 CI 对齐

复用现有安装 driver、update plan 和 use_installation_owner;新判断保持 provider-local,没有新共享词汇或授权模型。文档明确提前拒绝与已损坏包的 owner 修复,不把强制拒绝称为可忽略建议,也不把失败安装当作零变化。普通 POSIX、read-only 与模块模型覆盖了反向误拦风险。

我的整体评价

源码结构与规模 proportionate,单一 renderer 复用是合适的有界简化。problem_context、long_horizon、user_experience 和原生 observable_semantics 仍是 not_yet_proven:实际安装和后续任务能否在该普通构建中继续,需要原 issue 指定的真实边界证据。请补可独立核对的原生资格,不需要为了本意见增加源码框架或跨版本/新 backend 功能。当前没有新的阻塞性源码缺陷;REQUEST_CHANGES 是明确的验收 hold,并保留已通过源码与已报告原生证据的边界。

English verdict: REQUEST_CHANGES at 10d31911a3ceae51590df1a5e1ab07d78c926861. The bounded loader/same-file design and all12 independent source models pass, including saved-plan refusal and unrelated-entry parity. Author current-head native qualification is acknowledged, but its ordinary artifact, replayable native harness and package/task/recovery readback have not been independently verified in this review. Retain the initial120s install-smoke timeout and separate missing quality receipt; neither is silently relabeled as passed.

@BigDataDZ

Copy link
Copy Markdown
Contributor Author

当前 exact head 10d31911a3ceae51590df1a5e1ab07d78c926861 的原生 Windows 资格补充(作者资格证据,等待独立核证)。补齐可复制的隔离脚本和结构化任务/lease/receipt 读回;不改变产品源码,也不把作者验证升级为正式批准。

The ordinary wheel used below is loopx-2.0.0a1-py3-none-any.whl, SHA256 0bd5dab873336472251d7060cae0ae3631fd37ae26d509997371a5eb3d6be2b8. This is an immutable identity of the bytes tested locally; those bytes are not presently downloadable from this comment. The pinned-source recipe lets another reviewer build and qualify an ordinary wheel independently. A source-pinned rebuild is not a guarantee of the same wheel SHA256; retain its independently measured hash in the replay receipt.

Source under qualification. From a clone, with native Windows, Python 3.12, Node.js 24/npm, uv and PowerShell 7 (pwsh) available, save the script below as windows-wheel-replay.py outside the source checkout and run:

$qualificationSource = Join-Path ([IO.Path]::GetTempPath()) ('loopx-source-' + [guid]::NewGuid().ToString('N'))
$qualificationArtifacts = Join-Path ([IO.Path]::GetTempPath()) ('loopx-artifacts-' + [guid]::NewGuid().ToString('N'))
git fetch origin 10d31911a3ceae51590df1a5e1ab07d78c926861
git worktree add --detach $qualificationSource 10d31911a3ceae51590df1a5e1ab07d78c926861
Push-Location $qualificationSource
try {
    uv sync --python 3.12 --extra test
    npm.cmd --prefix apps/presentation/dashboard ci --ignore-scripts
    npm.cmd --prefix apps/presentation/dashboard run build:chat
    uv run --extra test python scripts/chat_bundle.py verify --source
    uv build --wheel --out-dir $qualificationArtifacts
} finally {
    Pop-Location
}
$qualificationWheels = @(Get-ChildItem -LiteralPath $qualificationArtifacts -Filter '*.whl')
if ($qualificationWheels.Count -ne 1) { throw 'Expected exactly one built wheel' }
$qualificationWheel = $qualificationWheels[0]
Get-FileHash -LiteralPath $qualificationWheel.FullName -Algorithm SHA256
python windows-wheel-replay.py --wheel $qualificationWheel.FullName --output replay-receipt.json --source-head 10d31911a3ceae51590df1a5e1ab07d78c926861

The build prepares checkout test dependencies and the normal packaged Chat frontend before invoking the standard wheel backend. The pinned setup.py verifies the frontend bundle; uv build alone on a fresh checkout is insufficient. Dependency preparation may fetch Python/npm/build dependencies. The replay receipt records the source head as a caller assertion, not a cryptographic wheel-to-source attestation. The replay itself performs an offline, same-version forced replacement from the supplied wheel. It creates a new disposable synthetic project, fixture home, venv, SQLite authority and effect-runtime namespace; no real Goal, provider credentials or user state is required. Each CLI operation starts a new process. All 1801 package source files are compared against the ordinary wheel before and after replacement. Refusal comparison excludes bytecode caches and transient .lock names. This does not qualify cross-version upgrade, arbitrary installation-failure rollback or the pipx uv backend.

Exact printed PowerShell module retry, actual pip uninstall/reinstall, all six stages, invalid completion rejection and valid UTF-8 completion with unchanged receipt replay passed in the attached structured readback. The genuine pipx 1.18.4 pip-backend suffixed loopx-preview journey was separately qualified earlier; the replay below qualifies pip, and does not imply that pipx was replayed by this script.

The separate exact-scope change-quality receipt remains a hold. The original 120-second install-local-smoke timeout remains recorded; this native qualification does not relabel that check. These author observations request independent review and do not assert approval or merge readiness.

Replay script (UTF-8/LF, one final newline; SHA256 98003e321e80270a3f6290d3cc145f069eb50322040db3c0d3b282475f8b57c9)
"""Isolated Windows pip qualification. Only a new synthetic temp tree is modified.
Requires Windows, Python with venv/pip, PowerShell, and an ordinary LoopX wheel.
No downloads: pip is restricted to the supplied wheel directory.
Output is a public-safe structured author qualification receipt, not approval.
"""
import argparse
import shutil
import tempfile
from pathlib import Path
import hashlib
import json
import os
import subprocess
import sys
import zipfile

parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--wheel', type=Path, required=True)
parser.add_argument('--output', type=Path, required=True)
parser.add_argument('--source-head', required=True)
parser.add_argument('--powershell', default='pwsh')
args = parser.parse_args()
if sys.platform != 'win32':
    parser.error('requires native Windows')
wheel_input = args.wheel.resolve(strict=True)
if wheel_input.suffix != '.whl':
    parser.error('--wheel must identify an ordinary wheel')
if args.output.exists():
    parser.error('--output already exists; choose a new receipt path')
sandbox = tempfile.TemporaryDirectory(prefix='loopx-native-qualification-')
root = Path(sandbox.name)
(root / 'tmp').mkdir()
(root / 'dist').mkdir()
shutil.copy2(wheel_input, root / 'dist' / wheel_input.name)
venv = root / 'venv 中文'
python = venv / 'Scripts/python.exe'
cli = venv / 'Scripts/loopx.exe'
project = root / 'synthetic-project'
runtime = root / 'runtime'
home = root / 'fixture-home'
registry = project / '.loopx/registry.json'
pwsh = args.powershell
env = dict(os.environ)
for key in list(env):
    if key.startswith(('LOOPX_', 'PIP_', 'PIPX_', 'UV_')) or key in {'CODEX_HOME', 'CLAUDE_CONFIG_DIR', 'VIRTUAL_ENV', 'PYTHONPATH', 'PYTHONHOME', 'NODE_OPTIONS', 'NODE_PATH', 'NODE_COMPILE_CACHE'}:
        env.pop(key)
env.update(HOME=str(home), USERPROFILE=str(home), CODEX_HOME=str(home / '.codex'),
           CLAUDE_CONFIG_DIR=str(home / '.claude'), LOOPX_USAGE_PING='0', PYTHONUTF8='1',
           APPDATA=str(root / 'appdata/roaming'), LOCALAPPDATA=str(root / 'appdata/local'),
           XDG_CONFIG_HOME=str(home / '.config'), XDG_CACHE_HOME=str(home / '.cache'),
           PIP_CONFIG_FILE=os.devnull, PIP_DISABLE_PIP_VERSION_CHECK='1', PIP_NO_INDEX='1', PIP_FIND_LINKS=str(root / 'dist'))
env['LOOPX_EFFECT_RUNTIME_DIR'] = str(root / 'effect')
env.update(TEMP=str(root / 'tmp'), TMP=str(root / 'tmp'), TMPDIR=str(root / 'tmp'))
env['PATH'] = str(python.parent) + os.pathsep + env.get('PATH', '')
common = ['--registry', str(registry), '--runtime-root', str(runtime), '--format', 'json']

def save(name, value):
    (root / name).write_text(json.dumps(value, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')

def run(command, label, *, expected=0, timeout=180, extra_env=None):
    result = subprocess.run(command, cwd=project, env={**env, **(extra_env or {})},
        capture_output=True, encoding='utf-8', errors='replace', timeout=timeout)
    (root / (label + '.stdout')).write_text(result.stdout, encoding='utf-8')
    (root / (label + '.stderr')).write_text(result.stderr, encoding='utf-8')
    save(label + '-command.json', {'argv': command, 'exit': result.returncode})
    assert result.returncode == expected, (label, result.returncode, result.stderr[-1600:], result.stdout[-1600:])
    return result

def call(args, label, expected=0):
    return json.loads(run([str(cli), *common, *args], label, expected=expected).stdout)

def file_hashes(base, *, package=False):
    result = {}
    for path in base.rglob('*'):
        if not path.is_file() or '__pycache__' in path.parts or '.lock' in path.name:
            continue
        result[str(path.relative_to(base))] = hashlib.sha256(path.read_bytes()).hexdigest()
    return result

def qualify(phase):
    if phase == 'install':
        assert not venv.exists(), 'refuse to overwrite an existing test environment'
        project.mkdir(); home.mkdir()
        wheels = list((root / 'dist').glob('*.whl'))
        assert len(wheels) == 1
        wheel = wheels[0]
        subprocess.run([sys.executable, '-m', 'venv', str(venv)], check=True, env=env)
        run([str(python), '-m', 'pip', 'install', '--no-index', '--no-deps', str(wheel)], 'install')
        probe = json.loads(run([str(python), '-I', '-c',
            'import importlib.metadata,json,loopx; print(json.dumps({"version":importlib.metadata.version("loopx"),"module":loopx.__file__}))'], 'provenance').stdout)
        assert Path(probe['module']).is_relative_to(venv)
        site = venv / 'Lib/site-packages'
        checked = 0
        with zipfile.ZipFile(wheel) as archive:
            for member in archive.namelist():
                if member.startswith('loopx/') and not member.endswith('/'):
                    assert archive.read(member) == (site / member).read_bytes(), member
                    checked += 1
        save('artifact.json', {'head': args.source_head,
            'wheel': wheel.name, 'wheel_sha256': hashlib.sha256(wheel.read_bytes()).hexdigest(),
            'installed_package_files_checked': checked, 'installed_provenance': probe,
            'boundary': 'ordinary full wheel; no overlays or package source modifications'})
        print('Ordinary wheel installed with exact archive/package parity:', checked, flush=True)
    elif phase == 'seed':
        goal = 'native-wheel-recovery'
        agent = 'synthetic-wheel-worker'
        call(['bootstrap', '--project', str(project), '--goal-id', goal,
              '--objective', 'Preserve a synthetic task through installed Windows module recovery'], 'bootstrap')
        call(['register-agent', '--goal-id', goal, '--agent-id', agent, '--require-new', '--execute'], 'register')
        validator = json.dumps([str(python), '-c', "from pathlib import Path; assert Path('result.txt').read_text(encoding='utf-8') == '恢复完成 🚀'"], ensure_ascii=False)
        todo = call(['todo', 'add', '--goal-id', goal, '--role', 'agent', '--text',
            '[P0] Recover and validate the synthetic artifact', '--task-class', 'advancement_task',
            '--claimed-by', agent, '--validation-command-json', validator,
            '--validation-label', 'Native installed recovery artifact'], 'todo-add')
        lease = call(['task-lease', 'acquire', '--goal-id', goal, '--todo-id', todo['todo_id'],
            '--owner', agent, '--idempotency-key', 'native-wheel-once'], 'lease-acquire')
        (project / 'result.txt').write_text('unfinished', encoding='utf-8')
        save('checkpoint.json', {'goal': goal, 'agent': agent, 'todo': todo['todo_id'],
            'lease_key': 'native-wheel-once', 'lease_version': lease['lease']['version']})
        print('Synthetic pending task and lease created through installed CLI.', flush=True)
    elif phase == 'guard':
        checkpoint = json.loads((root / 'checkpoint.json').read_text(encoding='utf-8'))
        package = venv / 'Lib/site-packages/loopx'
        before = {'package': file_hashes(package, package=True), 'launcher': hashlib.sha256(cli.read_bytes()).hexdigest(),
                  'project': file_hashes(project), 'runtime': file_hashes(runtime), 'home': file_hashes(home)}
        result = call(['update', 'apply', '--timeout-seconds', '120'], 'console-refusal', expected=1)
        after = {'package': file_hashes(package, package=True), 'launcher': hashlib.sha256(cli.read_bytes()).hexdigest(),
                 'project': file_hashes(project), 'runtime': file_hashes(runtime), 'home': file_hashes(home)}
        save('refusal-state-comparison.json', {'before': before, 'after': after, 'equal': before == after})
        assert result['ok'] is False and result['execution'] is None and result['changes_applied'] is False
        assert before == after, 'installed console refusal mutated protected fixture files'
        retry = result['next_action']['command']
        assert 'loopx.entrypoint' in retry and str(registry) in retry
        save('retry.json', {'command': retry})
        print('Real PE console refused; complete package, launcher, task/runtime and prompt files unchanged.', flush=True)
    elif phase == 'recover':
        retry = json.loads((root / 'retry.json').read_text(encoding='utf-8'))['command']
        # Force a real same-version package replacement from the exact candidate
        # artifact, without publishing a synthetic version or patching pip/LoopX.
        result = run([str(pwsh), '-NoLogo', '-NoProfile', '-NonInteractive', '-Command', retry + '; exit $LASTEXITCODE'],
            'module-recovery', timeout=840, extra_env={'PIP_FORCE_REINSTALL': '1'})
        payload = json.loads(result.stdout)
        save('module-recovery-result.json', payload)
        assert payload['ok'] is True and payload['changes_applied'] is True
        execution = payload['execution']
        for name in ['install', 'authority_upgrade', 'workflow_skills', 'slash_commands', 'doctor', 'extension_doctor']:
            assert execution[name + '_returncode'] == 0, (name, execution)
        assert 'Successfully uninstalled loopx-' in execution['install_stdout_tail'], execution
        print('Exact printed PowerShell command completed real pip reinstall and all six stages.', flush=True)
    elif phase == 'settle':
        c = json.loads((root / 'checkpoint.json').read_text(encoding='utf-8'))
        todo = call(['todo', 'list', '--goal-id', c['goal'], '--todo-id', c['todo']], 'pending-readback')
        lease = call(['task-lease', 'inspect', '--goal-id', c['goal'], '--todo-id', c['todo']], 'lease-readback')
        assert todo['todo']['status'] == 'open'
        assert lease['lease']['owner'] == c['agent'] and lease['lease']['version'] == c['lease_version']
        completion_args = ['todo', 'complete', '--goal-id', c['goal'], '--todo-id', c['todo'], '--agent-id', c['agent'],
            '--task-lease-idempotency-key', c['lease_key'], '--task-lease-expected-version', str(c['lease_version']),
            '--evidence', 'Synthetic UTF-8 artifact recovered after full-wheel replacement.', '--no-follow-up']
        invalid = call(completion_args, 'invalid-completion', expected=1)
        assert invalid['validation_blocked_completion'] is True
        (project / 'result.txt').write_text('恢复完成 🚀', encoding='utf-8')
        completed = call(completion_args, 'valid-completion')
        assert completed['completed'] is True and completed['completion_receipt_id']
        replay = call(completion_args, 'completion-replay')
        assert replay['changed'] is False and replay['completion_receipt_id'] == completed['completion_receipt_id']
        save('settlement-summary.json', {'task_and_lease_preserved': True, 'invalid_artifact_rejected': True,
            'valid_completion': True, 'replay_changed': False})
        print('Installed task/lease preserved; invalid completion rejected; valid receipt replay unchanged.', flush=True)
    else:
        raise ValueError(phase)

try:
    for phase in ('install', 'seed', 'guard', 'recover', 'settle'):
        qualify(phase)
    def load(name):
        return json.loads((root / name).read_text(encoding='utf-8'))
    artifact = load('artifact.json')
    comparison = load('refusal-state-comparison.json')
    pending = load('pending-readback.stdout')
    lease = load('lease-readback.stdout')
    invalid = load('invalid-completion.stdout')
    valid = load('valid-completion.stdout')
    replay = load('completion-replay.stdout')
    recovery = load('module-recovery-result.json')
    checkpoint = load('checkpoint.json')
    assert checkpoint['todo'] == pending['todo']['todo_id'] == lease['lease']['todo_id'] == valid['todo_id'] == replay['todo_id']
    assert checkpoint['goal'] == lease['lease']['goal_id']
    assert checkpoint['lease_version'] == lease['lease']['version']
    assert pending['authority_read']['source_authority'] == 'sqlite_v0'
    assert lease['handoff_mode'] == 'hard_lease'
    assert cli.read_bytes()[:2] == b'MZ'
    installed = venv / 'Lib/site-packages'
    with zipfile.ZipFile(wheel_input) as archive:
        members = [n for n in archive.namelist() if n.startswith('loopx/') and not n.endswith('/')]
        assert all(archive.read(n) == (installed / n).read_bytes() for n in members)
    # Every call uses a new process; probe again after actual replacement.
    provenance = json.loads(run([str(python), '-I', '-c',
        'import json,loopx; print(json.dumps({"module":loopx.__file__}))'], 'final-provenance').stdout)
    assert Path(provenance['module']).is_relative_to(venv)
    receipt = {
        'source_head_asserted_by_caller': args.source_head,
        'wheel_name': wheel_input.name, 'wheel_sha256': artifact['wheel_sha256'],
        'package_files_equal_before_and_after_replacement': len(members),
        'real_pe_console': True,
        'refusal': {'exit': 1, 'all_compared_files_unchanged': comparison['equal'],
            'compared_counts': {k: len(v) if isinstance(v, dict) else 1 for k,v in comparison['before'].items()}},
        'recovery': {'exact_printed_powershell_command': True, 'offline_same_version_real_pip_reinstall': True,
            'six_stage_exits': {k: recovery['execution'][k + '_returncode'] for k in
                ('install','authority_upgrade','workflow_skills','slash_commands','doctor','extension_doctor')}},
        'task': {'source_authority': pending['authority_read']['source_authority'],
            'todo_id': pending['todo']['todo_id'],
            'status_after_recovery': pending['todo']['status'], 'lease_mode': lease['handoff_mode'],
            'lease_goal_id': lease['lease']['goal_id'], 'lease_todo_id': lease['lease']['todo_id'],
            'lease_owner': lease['lease']['owner'], 'lease_version': lease['lease']['version'],
            'lease_active': lease['active'], 'lease_owner_matches': lease['lease']['owner'] == 'synthetic-wheel-worker',
            'invalid_completion_blocked': invalid['validation_blocked_completion'],
            'valid_utf8_completion': valid['completed'], 'receipt_present': bool(valid['completion_receipt_id']),
            'completion_todo_id': valid['todo_id'], 'completion_receipt_id': valid['completion_receipt_id'],
            'replay_changed': replay['changed'],
            'replay_todo_id': replay['todo_id'], 'replay_receipt_id': replay['completion_receipt_id'],
            'replay_same_receipt': replay['completion_receipt_id'] == valid['completion_receipt_id']},
        'fresh_process_isolated_import': True,
        'limits': ['pip only; genuine pipx requires separate qualification',
            'same-version forced reinstall, not cross-version upgrade or arbitrary failure rollback',
            'bytecode caches and transient .lock names excluded from refusal hash accounting',
            'caller-supplied source pin must be independently checked against wheel build',
            'qualification is not formal review approval or change-quality receipt']}
    args.output.resolve().write_text(json.dumps(receipt, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
    print('Public-safe structured receipt:', args.output.name)
finally:
    try:
        if (venv / 'Lib/site-packages/loopx').is_dir():
            run([str(python), '-I', '-c',
                 'from loopx.control_plane.effect_runtime import restart_effect_runtime; restart_effect_runtime(wait_for_exit=True)'],
                'owned-runtime-cleanup', timeout=30)
    finally:
        sandbox.cleanup()
Structured native replay readback
{
  "source_head_asserted_by_caller": "10d31911a3ceae51590df1a5e1ab07d78c926861",
  "wheel_name": "loopx-2.0.0a1-py3-none-any.whl",
  "wheel_sha256": "0bd5dab873336472251d7060cae0ae3631fd37ae26d509997371a5eb3d6be2b8",
  "package_files_equal_before_and_after_replacement": 1801,
  "real_pe_console": true,
  "refusal": {
    "exit": 1,
    "all_compared_files_unchanged": true,
    "compared_counts": {
      "package": 1801,
      "launcher": 1,
      "project": 3,
      "runtime": 9,
      "home": 0
    }
  },
  "recovery": {
    "exact_printed_powershell_command": true,
    "offline_same_version_real_pip_reinstall": true,
    "six_stage_exits": {
      "install": 0,
      "authority_upgrade": 0,
      "workflow_skills": 0,
      "slash_commands": 0,
      "doctor": 0,
      "extension_doctor": 0
    }
  },
  "task": {
    "source_authority": "sqlite_v0",
    "todo_id": "todo_d6a72115954e641751e63b7a",
    "status_after_recovery": "open",
    "lease_mode": "hard_lease",
    "lease_goal_id": "native-wheel-recovery",
    "lease_todo_id": "todo_d6a72115954e641751e63b7a",
    "lease_owner": "synthetic-wheel-worker",
    "lease_version": 1,
    "lease_active": true,
    "lease_owner_matches": true,
    "invalid_completion_blocked": true,
    "valid_utf8_completion": true,
    "receipt_present": true,
    "completion_todo_id": "todo_d6a72115954e641751e63b7a",
    "completion_receipt_id": "tcw_96cf3c368040347cb3a5fbe2dc20dcc6765938f862e0c5af50645dec8db336c4",
    "replay_changed": false,
    "replay_todo_id": "todo_d6a72115954e641751e63b7a",
    "replay_receipt_id": "tcw_96cf3c368040347cb3a5fbe2dc20dcc6765938f862e0c5af50645dec8db336c4",
    "replay_same_receipt": true
  },
  "fresh_process_isolated_import": true,
  "limits": [
    "pip only; genuine pipx requires separate qualification",
    "same-version forced reinstall, not cross-version upgrade or arbitrary failure rollback",
    "bytecode caches and transient .lock names excluded from refusal hash accounting",
    "caller-supplied source pin must be independently checked against wheel build",
    "qualification is not formal review approval or change-quality receipt"
  ]
}

This branch has not been deployed

No deployments
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.

Windows: pip self-update through loopx.exe can fail with WinError 32 and leave the CLI unimportable

2 participants