Skip to content

chore(发布): 准备 0.7.15 - #67

Merged
Dandre126 merged 11 commits into
mainfrom
feat/dev_0814
Sep 11, 2026
Merged

chore(发布): 准备 0.7.15#67
Dandre126 merged 11 commits into
mainfrom
feat/dev_0814

Conversation

@Dandre126

Copy link
Copy Markdown
Contributor

本版两条对外改动

1. dyro dispatch 不再因并发读取运行状态而偶发失败

RunStore.load_read_payload 读运行状态文件:先 os.open 拿 fd,再 os.stat(path, follow_symlinks=False),用 os.path.samestat 比对 inode。写入侧 _save_unlockedatomic_write_json,以 os.replace 发布更新;读取侧不持写锁。异步 worker 的一次合法更新若落在 open 与复查之间,inode 就变了,守卫抛 DispatchValidationErrorcli.py 转成退出码 2。

症状是 dyro dispatch run 间歇性失败,stderr 为 run state path changed while opening: run-<id>。这不只是测试偶发:任何在 worker 更新状态时并发读取的真实运行都可能踩到。

单次读取抽出为 _read_run_state_bytes,inode 不匹配返回 None_read_payload 有界重试 RUN_STATE_REOPEN_ATTEMPTS(5)次,用尽后仍按原语义拒绝。合法的 os.replace 一两次即稳定,持续变化的路径依旧被拒。

安全属性未放宽O_NOFOLLOW 在每次尝试上生效,fstat 仍校验普通文件与大小上限,符号链接仍拒绝,读取仍只走已打开的 fd。可重试的只有「仍是打开时那个 inode」一项,因为合法原子写入触发它的方式与路径掉包完全相同。文件被换成符号链接时,重试以 ELOOP 失败,报出更准确的 run state is a symbolic link

2. console 子系统改为延迟导入

cli.py 在模块作用域 import console.launcher(其后是 inspection/events/overview),而只有 dyro console 用得到。移入 cmd_console 函数体,与 home.py 既有写法一致。

实测收益有限:dyro.cli 累计 import 由 225-236ms 降到 210-216ms,约 20ms-X importtimeconsole.launcher 记的 81ms 是含子树累计值,而 console 的下游(continuation.*observations)本来就被 cli.py 的其他依赖拉入。已确认 dyro.console 完全不在启动 import 图中。

验证

新增两条确定性回归测试(ProcessAndLifecycleTests):

  • test_run_state_read_survives_a_concurrent_atomic_replace — 在读取者 os.open 之后、inode 复查之前真实执行一次 atomic_write_json,断言读取成功且拿到更新后的内容
  • test_run_state_read_still_refuses_a_path_that_keeps_changing — 每次尝试都替换,断言仍然拒绝且用满重试次数

RUN_STATE_REOPEN_ATTEMPTS 置为 1(等价修复前)时,前者复现出与 CI 一字不差的 run state path changed while opening: run-...,确认该测试能抓到本缺陷。

tests/test_hub.pypatch("dyro.cli.launch_console") 随延迟导入改为 patch 源模块 dyro.console.launcher.launch_console,对 eager / lazy 两种写法都成立。

本地锁定验证:

uv lock --check
uv sync --locked --all-extras --dev
uv run ruff check src tests experiments      → All checks passed!
uv run python -m unittest discover           → Ran 1125 tests,失败集合与基线一致
uv run python -m build                       → dyro-0.7.15.tar.gz / dyro-0.7.15-py3-none-any.whl
uv run python -m twine check --strict dist/* → PASSED / PASSED

基线上既有的两条环境敏感失败(test_inspect_timeout_reaps_hung_descendantstest_legacy_board_status_stays_on_protocol_id)与本次改动无关,无新增失败。

发布准备

docs/publishing.md 同步了全部 7 处版本绑定。uv lock 期间 PyPI 连接超时,改用 --offline 完成;本次只有项目版本号变化,lock diff 仅一行,uv lock --check 复核通过。

合入后需要在 main 的对应 SHA 上打 v0.7.15、创建 GitHub Release。

未 push 的开发线不再把 next/start 卡成 needs_repair;task close 拒绝符号链接工作树;dry-run 与复核拒绝按契约执行。
task loop/daemon 在复核失败时非零退出;事件追加失败标记 overlay 不完整;merge --dry-run --push 改为真实预检。
把 feat/dev_0814 上的控制面诚实性与任务关闭收口收进 0.7.13。
版本停在 0.7.x,不改 0.7.12 历史,不发明新功能。
next / Console 在仅缺 origin/<branch> 时保持 ready;测试改为断言 WARN 契约。
doctor() 原先对每个 (开发线, 仓库) 组合串行发约 8 条 git 命令。慧衣橱工作区
(5 个仓库 × 13 条开发线)一次需要 399 次子进程调用、耗时 5.6s;首页菜单与
开发线准入闸门各跑一轮,进一条开发线约 11s。

改为按仓库预取两份观测:

- git worktree list --porcelain 一次取回该仓库全部 worktree 的路径、HEAD 与
  分支,取代逐 worktree 的 branch --show-current、rev-parse HEAD 以及成对的
  rev-parse --git-common-dir。出现在这份清单里本身就是共用 Git 公共目录的
  证据。
- git for-each-ref refs/heads refs/remotes/origin 一次取回每个本地分支的上游
  与每个引用的对象 ID,取代逐开发线的 show-ref、rev-parse origin/<branch> 与
  rev-parse @{upstream}。

worktree 不在清单里时回退到原有的单次探测,"missing worktree" 与
"unexpected Git common-dir" 两种诊断仍可区分。ReadBudget 的字节计费与 deadline
照常穿透,观测点由每条开发线移到每个仓库。

慧衣橱工作区的调用次数由 399 降到 16,耗时由 5.61s 降到 0.30s;四个真实工作区
的 findings 列表(含顺序)与改动前逐条相同。

测试故障注入点 _raise_deadline_on_worktree 同步扩展为「路径含 versions/ 或为
git worktree 调用」,新旧两版代码均通过,被测不变量未变。
把版本号推进到 0.7.14,并同步所有与版本绑定的断言与资产:
`pyproject.toml`、`uv.lock`、bridge skill 的 client version,以及
`test_cli`、`test_console_artifacts`、`test_release_gates` 中的版本断言。
CHANGELOG 记录本版唯一的对外改动:doctor 改用批量 Git 观测。

`uv lock` 在更新项目版本的同时,把 `importlib-metadata` 对 `zipp` 的依赖
标记收紧为 `python_full_version < '3.14'`。这是本机 uv 重解析出的上游
metadata 修正,没有任何包版本变化。

本地锁定验证:`uv lock --check`、`uv sync --locked --all-extras --dev`、
`ruff check src tests experiments`、`python -m build`、
`twine check --strict` 全部通过。
main 上的 #65 以 squash 方式合入,使本分支与 main 在 0.7.13 的版本绑定
文件上产生同容不同史的冲突。合并前已验证 git diff origin/main a417f22
为空,即 main 的树与本分支合入 0.7.13 之后的树逐字相同,main 不含本分支
缺少的任何内容;冲突解析一律取本分支侧,合并后树与合并前 HEAD 相同。
`cli.py` 在模块作用域 import `console.launcher`,把 inspection、events、
overview 整条链拉进每一次 `dyro` 启动,而它只有 `cmd_console` 用得到。
改为在 `cmd_console` 函数体内 import,与 `home.py` 既有写法一致。

实测收益有限:空闲机器上 `dyro.cli` 的累计 import 时间由 225-236ms 降到
210-216ms,约 20ms。`-X importtime` 给 `console.launcher` 记的 81ms 是含
子树的累计值,而 console 的下游(`continuation.*`、`observations`)本来
就被 cli.py 的其他依赖拉入,摘掉 console 摘不掉它们。验证 `dyro.console`
已完全不在启动 import 图中。

`tests/test_hub.py` 的 `patch("dyro.cli.launch_console")` 随之改为 patch
源模块 `dyro.console.launcher.launch_console`。延迟导入在调用时才解析属性,
patch 源模块对 eager 与 lazy 两种写法都成立。

全量 1123 条测试,失败集合与改动前基线一致,无新增失败。
`RunStore.load` 经 `_read_payload` 读运行状态文件时,先 `os.open` 拿到
fd,再 `os.stat(path, follow_symlinks=False)` 复查,用 `os.path.samestat`
比对 inode。写入侧 `_save_unlocked` 走 `atomic_write_json`,以
`os.replace` 发布更新。读取侧 `load` 不持锁,因此异步 worker 的一次合法
更新若落在 open 与复查之间,inode 就会变化,守卫抛
`DispatchValidationError`,`cli.py` 将其转为退出码 2,运行随之失败。

这不只是测试偶发:任何在 worker 更新状态时并发读取的真实 dispatch 运行
都可能踩到。CI 上表现为 test_default_cli_run_starts_async_worker 间歇性
失败,stderr 为 `run state path changed while opening: run-...`。

单次读取抽出为 `_read_run_state_bytes`,inode 不匹配时返回 None;
`_read_payload` 有界重试 `RUN_STATE_REOPEN_ATTEMPTS` 次,用尽后仍按原有
语义拒绝。合法的 `os.replace` 一两次即稳定,持续变化的路径依旧被拒。

安全属性未放宽:`O_NOFOLLOW` 在每次尝试上生效,`fstat` 仍校验普通文件与
大小上限,符号链接仍拒绝,读取仍只走已打开的 fd。可重试的只有「仍是打开
时那个 inode」这一项,因为合法原子写入触发它的方式与路径掉包完全相同。
若文件被换成符号链接,重试时 `O_NOFOLLOW` 以 ELOOP 失败,报出更准确的
`run state is a symbolic link`。

新增两条回归测试:一条在 open 与复查之间真实执行 `atomic_write_json`,
断言读取成功且拿到更新后的内容;一条每次尝试都替换,断言仍然拒绝且用满
重试次数。把 RUN_STATE_REOPEN_ATTEMPTS 置为 1 时,前者复现出与 CI 一致的
报错,确认该测试能抓到本缺陷。

全量 1125 条测试,失败集合与改动前基线一致;ruff src tests experiments
通过。
main 上的 #66 以 squash 方式合入,本分支与 main 再次同容不同史。合并前
已确认 git diff origin/main HEAD 只含本分支尚未发布的 4 个文件,main 不
含本分支缺少的内容;冲突解析取本分支侧,合并后树与合并前 HEAD 相同。
把版本号推进到 0.7.15,并同步所有与版本绑定的断言与资产:
`pyproject.toml`、`uv.lock`、bridge skill 的 client version,以及
`test_cli`、`test_console_artifacts`、`test_release_gates` 中的版本断言。

CHANGELOG 记录本版两条对外改动:dispatch 运行状态读取不再把原子写入误判
为路径掉包(修复 `dyro dispatch` 偶发退出码 2),以及 console 子系统改为
延迟导入。

本地锁定验证:`uv lock --check`、`uv sync --locked --all-extras --dev`、
`ruff check src tests experiments`、1125 条测试、`python -m build`、
`twine check --strict` 全部通过;测试失败集合与本版改动前的基线一致。

`uv lock` 期间 PyPI 连接超时,改用 `--offline` 完成。本次只有项目版本号
变化,依赖已解析并缓存,lock diff 仅一行,`uv lock --check` 复核通过。
@vercel

vercel Bot commented Sep 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
dyro Error Error Sep 11, 2026 5:41pm UTC
dyro-engineering-flow Error Error Sep 11, 2026 5:41pm UTC

@Dandre126
Dandre126 merged commit 91c972d into main Sep 11, 2026
9 of 11 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.

1 participant