Conversation
Issue loopx-project#4336 reported that releases through 0.4.5 inferred autonomous replan stalls from free-form run summaries with a substring matcher, so the word 'stalled' inside 'installed' turned successful progress records into a no_progress_streak trigger and an erroneous autonomous_replan_required decision. loopx-project#3161 removed that prose matcher; main sources replan decisions from typed progress observations only. Pin the removal through the real public CLI instead of private helper state: run records whose summary prose contains 'installed', 'uninstalled', and 'installation completed' keep the should-run decision at 'run' with no stall trigger in the public payload, while the contrast case proves two typed unchanged observations with the same fingerprint still require replan through typed_progress_repeat. Surface a bounded, version-aware advisory for installations still on an affected release: loopx doctor now carries a release_advisories section (loopx_release_advisories_v0) that identifies runtimes at or below 0.4.5 as affected by the legacy matcher and points to 0.4.6 and the standard update flow. The advisory describes runtime behavior only and never interprets the user's project text; fixed releases render no section. Fixes loopx-project#4336 Signed-off-by: BigDataDZ <76271875+BigDataDZ@users.noreply.github.com>
huangruiteng
left a comment
There was a problem hiding this comment.
动机
issue #4336 的问题是历史行为:直到 0.4.5,LoopX 用一段 prose 正则去推断"自主 replan 卡住了"。我在 tag 上核对了这个说法:v0.4.5 的 loopx/control_plane/work_items/autonomous_replan_obligation.py 里确实有一段 ...|stalled?|stall streak|... 的忽略大小写正则作用在合并后的 run summary 上,installed/installation 里的 stall 就会命中;v0.4.6 里这段匹配已经不存在,注释写着"不要从 prose 字段推断 monitor/stall 状态"。也就是说主干上的分类逻辑早就是 typed-only,#3161 已经修掉了。所以这个 PR 的"修 bug"部分其实是把已经正确的行为用真实 CLI 钉住(我在 base c979cf11c 上把新测试文件原样跑了一遍:2 passed,它不回归任何东西),真正新增的用户可见价值是第二半:让还装在受影响版本上的 operator 能从 loopx doctor 里知道"我在受影响区间、第一个修好的版本是 0.4.6"。我核对过这个边界不是编的:v0.4.6 是真实 tag,docs/product/release-readiness.md:493 也记录了这次发布。所以动机成立,问题在于交付物里混进了一个跟这两件事都无关的文件。
改动思路
新增一个叶子模块 loopx/release_advisories.py(70 行,一张表 + 一个版本比较),loopx/doctor.py 里加一个 payload 段和一个 markdown 块。我特意查了它是不是和既有的 install_freshness/upgrade_hint 重复:不重复。install_freshness 回答的是"我的安装是不是旧了",用的是同一个版本权威 __version__(doctor 里 install_freshness.current_version 也是它),给出通用的升级命令;advisory 回答的是"这个具体缺陷影响哪些版本、第一个修好的版本是谁"。两者只在升级命令那一句话上重叠,而那句话是从既有的 loopx update check / loopx update apply 流程组合出来的,没有引入第二套升级机制、也没有新增第二处版本权威——这一点是本 PR 做得对的地方。真正的弱点在分类机制:_version_tuple 用 re.findall(r"\d+") 取出"每一段数字",于是后缀会变成额外的元组分量,比较语义就错了。
关键代码讲解
loopx/release_advisories.py:15_VERSION_TOKEN/_version_tuple:把版本串变成可比元组。0.4.5→(0,4,5),但0.4.5.post1/0.4.5rc1/0.4.5.dev1→(0,4,5,1),0.4.6rc1→(0,4,6,1)。我在 head 上直接探过:release_advisories_for("0.4.5.post1")、("0.4.5rc1")、("0.4.6rc1")全部返回(),而("0.4.5")返回一条。方向是反的两种:受影响版本的 dev/post 构建被当成"更新",没修好的 rc 被当成"已修"。loopx/release_advisories.py:29_RELEASE_ADVISORIES/release_advisories_for:一张手维护的边界表,命中时才补上guidance。表本身是结构化、可审计的,边界也与 tag 对得上;它的长期成本在于"发布史的一部分活在代码里",每加一行都要像这次一样回去对 tag。loopx/doctor.py:827collect_doctor的release_advisories段:加在install_freshness/upgrade_hint旁边,复用同一个__version__;render_doctor_markdown侧的循环把## Release Advisory标题写在了 per-advisory 的循环体内(loopx/doctor.py:1292-1310),现在只有一条所以看不出来,第二条 advisory 命中时会重复输出标题和current_version。
对主干的风险
最大的风险不是逻辑而是仓库资产边界:这个 PR 在根目录新增了 uv.lock(+1331 行),占整个 diff 的 79%。我核对过:git cat-file -e origin/main:uv.lock 显示主干从来没有这个文件;git check-ignore -v uv.lock 没有任何输出(也就是说它既没被 ignore、也没被采纳);.github/workflows/、docs/、CONTRIBUTING.md 里没有任何地方引用根 lockfile 或 uv sync;而且它的 blob 与同作者的 #4396、#4398 里那份逐字节相同(f4214968237e3ee32a285c99d40839523bace0f3),它记的 loopx 版本还是 1.0.3,而 main 的 pyproject 已经是 1.0.4。这个形态基本就是本地 uv 跑出来的副产物被顺手提交了。按仓库"compress rather than append / 不要捆绑与当前目的无关的工件"的要求,这个文件不该跟着这个 PR 进来。
第二个是分类器的边界错误(P3,非阻塞):受影响版本的 dev/post/rc 后缀会让 advisory 静默消失,恰恰是最可能在跑受影响版本的那批构建;反向则是提前宣布 0.4.6rc1 已修。修法很小:只比较 release 段(先按 +/- 切掉后缀),或者直接用版本解析器,并补一组参数化用例把两个方向都钉住。
验证(exact head 7e19230ee):
pytest tests/test_release_advisories.py tests/control_plane/test_quota_should_run_prose_contract.py -q→ 6 passed。- 同一份 prose 测试文件复制到 base
c979cf11c跑 → 2 passed,说明这半边是"钉住既有行为",不是修 bug。 - tag 对照:
git show v0.4.5:loopx/control_plane/work_items/autonomous_replan_obligation.py有 prose 正则,v0.4.6没有。 - 版本边界探针:
0.4.5命中,0.4.5.post1/0.4.5rc1/0.4.5.dev1/0.4.6rc1全部不命中。 - 远端检查:
gh pr checks 4397在7e19230ee上全绿(test-shard ×4、pytest、stage2c、windows-powershell、node 兼容矩阵等)。
我的整体评价
诊断面的方向是对的、边界是真的、新增代码比例也合理(生产侧 93 行 + 两组测试),release_advisories 不复用 install_freshness 也不算重复权威——这两点我确认过。所以我把 change_proportionality 记为 proportionate。但当前这个 diff 还不能合:请先删掉 uv.lock(#4396/#4398 里那份相同 blob 也一并处理),并把版本边界比较从"取每一段数字"改成"比较 release 段",补上后缀版本的用例。这两点做完之后,剩下的只是排版细节(把 advisory 标题提到循环外)。我这一轮不做任何合并动作。
English verdict: REQUEST_CHANGES at exact head 7e19230. The prose half is verified as a pin (the same test passes at origin/main), the advisory's 0.4.5→0.4.6 boundary matches the released tags, and the new doctor section correctly reuses the existing __version__ authority instead of creating a second one. Two blockers: the PR adds an unrelated, ungoverned root uv.lock (1331 lines, absent from main, not gitignored, referenced by no workflow/doc, byte-identical to the file in the sibling PRs #4396/#4398 and already stale against main), and _version_tuple misfiles suffixed versions so affected 0.4.5.post1/rc1/dev1 builds get no advisory while 0.4.6rc1 is reported as fixed.
huangruiteng
left a comment
There was a problem hiding this comment.
说明:这是同一 exact head 的重发版。上一版 review 漏掉了
## 具体改动分节标题,导致评审结论被判为格式不合规;正文内容、证据与结论均未改变。
动机
issue #4336 的问题是历史行为:直到 0.4.5,LoopX 用一段 prose 正则去推断"自主 replan 卡住了"。我在 tag 上核对了这个说法:v0.4.5 的 loopx/control_plane/work_items/autonomous_replan_obligation.py 里确实有一段 ...|stalled?|stall streak|... 的忽略大小写正则作用在合并后的 run summary 上,installed/installation 里的 stall 就会命中;v0.4.6 里这段匹配已经不存在,注释写着"不要从 prose 字段推断 monitor/stall 状态"。也就是说主干上的分类逻辑早就是 typed-only,#3161 已经修掉了。所以这个 PR 的"修 bug"部分其实是把已经正确的行为用真实 CLI 钉住(我在 base c979cf11c 上把新测试文件原样跑了一遍:2 passed,它不回归任何东西),真正新增的用户可见价值是第二半:让还装在受影响版本上的 operator 能从 loopx doctor 里知道"我在受影响区间、第一个修好的版本是 0.4.6"。我核对过这个边界不是编的:v0.4.6 是真实 tag,docs/product/release-readiness.md:493 也记录了这次发布。所以动机成立,问题在于交付物里混进了一个跟这两件事都无关的文件。
改动思路
新增一个叶子模块 loopx/release_advisories.py(70 行,一张表 + 一个版本比较),loopx/doctor.py 里加一个 payload 段和一个 markdown 块。我特意查了它是不是和既有的 install_freshness/upgrade_hint 重复:不重复。install_freshness 回答的是"我的安装是不是旧了",用的是同一个版本权威 __version__(doctor 里 install_freshness.current_version 也是它),给出通用的升级命令;advisory 回答的是"这个具体缺陷影响哪些版本、第一个修好的版本是谁"。两者只在升级命令那一句话上重叠,而那句话是从既有的 loopx update check / loopx update apply 流程组合出来的,没有引入第二套升级机制、也没有新增第二处版本权威——这一点是本 PR 做得对的地方。真正的弱点在分类机制:_version_tuple 用 re.findall(r"\d+") 取出"每一段数字",于是后缀会变成额外的元组分量,比较语义就错了。
具体改动
关键代码讲解
loopx/release_advisories.py:15_VERSION_TOKEN/_version_tuple:把版本串变成可比元组。0.4.5→(0,4,5),但0.4.5.post1/0.4.5rc1/0.4.5.dev1→(0,4,5,1),0.4.6rc1→(0,4,6,1)。我在 head 上直接探过:release_advisories_for("0.4.5.post1")、("0.4.5rc1")、("0.4.6rc1")全部返回(),而("0.4.5")返回一条。方向是反的两种:受影响版本的 dev/post 构建被当成"更新",没修好的 rc 被当成"已修"。loopx/release_advisories.py:29_RELEASE_ADVISORIES/release_advisories_for:一张手维护的边界表,命中时才补上guidance。表本身是结构化、可审计的,边界也与 tag 对得上;它的长期成本在于"发布史的一部分活在代码里",每加一行都要像这次一样回去对 tag。loopx/doctor.py:827collect_doctor的release_advisories段:加在install_freshness/upgrade_hint旁边,复用同一个__version__;render_doctor_markdown侧的循环把## Release Advisory标题写在了 per-advisory 的循环体内(loopx/doctor.py:1292-1310),现在只有一条所以看不出来,第二条 advisory 命中时会重复输出标题和current_version。
对主干的风险
最大的风险不是逻辑而是仓库资产边界:这个 PR 在根目录新增了 uv.lock(+1331 行),占整个 diff 的 79%。我核对过:git cat-file -e origin/main:uv.lock 显示主干从来没有这个文件;git check-ignore -v uv.lock 没有任何输出(也就是说它既没被 ignore、也没被采纳);.github/workflows/、docs/、CONTRIBUTING.md 里没有任何地方引用根 lockfile 或 uv sync;而且它的 blob 与同作者的 #4396、#4398 里那份逐字节相同(f4214968237e3ee32a285c99d40839523bace0f3),它记的 loopx 版本还是 1.0.3,而 main 的 pyproject 已经是 1.0.4。这个形态基本就是本地 uv 跑出来的副产物被顺手提交了。按仓库"compress rather than append / 不要捆绑与当前目的无关的工件"的要求,这个文件不该跟着这个 PR 进来。
第二个是分类器的边界错误(P3,非阻塞):受影响版本的 dev/post/rc 后缀会让 advisory 静默消失,恰恰是最可能在跑受影响版本的那批构建;反向则是提前宣布 0.4.6rc1 已修。修法很小:只比较 release 段(先按 +/- 切掉后缀),或者直接用版本解析器,并补一组参数化用例把两个方向都钉住。
验证(exact head 7e19230ee):
pytest tests/test_release_advisories.py tests/control_plane/test_quota_should_run_prose_contract.py -q→ 6 passed。- 同一份 prose 测试文件复制到 base
c979cf11c跑 → 2 passed,说明这半边是"钉住既有行为",不是修 bug。 - tag 对照:
git show v0.4.5:loopx/control_plane/work_items/autonomous_replan_obligation.py有 prose 正则,v0.4.6没有。 - 版本边界探针:
0.4.5命中,0.4.5.post1/0.4.5rc1/0.4.5.dev1/0.4.6rc1全部不命中。 - 远端检查:
gh pr checks 4397在7e19230ee上全绿(test-shard ×4、pytest、stage2c、windows-powershell、node 兼容矩阵等)。
我的整体评价
诊断面的方向是对的、边界是真的、新增代码比例也合理(生产侧 93 行 + 两组测试),release_advisories 不复用 install_freshness 也不算重复权威——这两点我确认过。所以我把 change_proportionality 记为 proportionate。但当前这个 diff 还不能合:请先删掉 uv.lock(#4396/#4398 里那份相同 blob 也一并处理),并把版本边界比较从"取每一段数字"改成"比较 release 段",补上后缀版本的用例。这两点做完之后,剩下的只是排版细节(把 advisory 标题提到循环外)。我这一轮不做任何合并动作。
English verdict: REQUEST_CHANGES at exact head 7e19230. The prose half is verified as a pin (the same test passes at origin/main), the advisory's 0.4.5→0.4.6 boundary matches the released tags, and the new doctor section correctly reuses the existing __version__ authority instead of creating a second one. Two blockers: the PR adds an unrelated, ungoverned root uv.lock (1331 lines, absent from main, not gitignored, referenced by no workflow/doc, byte-identical to the file in the sibling PRs #4396/#4398 and already stale against main), and _version_tuple misfiles suffixed versions so affected 0.4.5.post1/rc1/dev1 builds get no advisory while 0.4.6rc1 is reported as fixed.
Fixes #4336
What
Releases through 0.4.5 inferred autonomous-replan stalls from free-form run summaries with a substring matcher, so the word
stalledinsideinstalledturned successful progress records into ano_progress_streaktrigger and an erroneousautonomous_replan_requireddecision. #3161 removed the prose matcher on main; this PR pins that removal through public CLI behavior and closes the operator-facing gap for installations still on an affected release.installed,uninstalled, andinstallation completedkeepquota should-runatrunwith no stall trigger anywhere in the public payload.unchangedobservations sharing one fingerprint still require replan viatyped_progress_repeat— the typed stall path is independent of summary text.loopx doctornow carries arelease_advisoriessection (loopx_release_advisories_v0). A runtime at or below 0.4.5 is identified as affected by the legacy matcher and pointed to 0.4.6 plus the standardloopx update check/loopx update applyflow. Advisories describe runtime behavior only and never interpret the user's project text; fixed releases render no section.Verification
loopx doctorsmoke on 1.0.3: the payload carries an empty advisory section and the markdown renders no advisory.