Repository navigation
fix(turn-driver): ask the host for its no-follow flags - #8083
Conversation
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent; gpt-6.1-sol; OpenAI; runtime_reported; reasoning_effort=max.
动机 / Motivation
读取 Codex 原生诊断、历史恢复报告,或轮换 checkpoint 的操作者与 Agent。 在缺少 POSIX 属性的宿主上,原读取器先抛 AttributeError;改动使普通诊断可读、恢复缺所有者模型时返回类型化拒绝。 这能让诊断和恢复失败更可解释,但目标还包括保留原来的安全文件读取与 checkpoint 恢复保证。已独立验证普通文件与既有恢复流程,但无标志路径的链接安全和目录同步仍未满足。 不启用追踪、不运行模型、不改变 Goal、调度或配额,也不资格化新的恢复平台。
本次独立审阅完整 exact head 71d4ac4cb5cdc44c717ff163ffbf282575543d56,不可变 base bd79adc649680818f44f7c5f7e5743645309fc3d。没有把另一份评审、作者测试或 CI 元数据当作独立验收。
改动思路 / Approach
三个已有 IO owner 的放置与复用合适;缺失能力不能把安全拒绝或持久化步骤降为成功。 当前 PR 应交付这三处安全的跨平台降级与同一组守卫;修复 descriptor 身份校验和目录同步即可,无需新协议、状态或执行器。 Python 继续负责平台 IO,原 TypeScript artifact owner 继续选择、验证 checkpoint;不引入第二套调度、计分或生命周期权威。普通缺属性输入不再崩溃这一点有正向证据,但不能用“能打开”代替安全打开,也不能用函数返回代替同步完成。
| 独立同源场景 | base | 当前 head |
|---|---|---|
| 原生 flags、正常输入/轮换 | 普通读取;删除2并进行6次 bank/trash fsync | 相同 |
| 缺 no-follow、静态链接 | AttributeError | 类型化拒绝 |
| 缺 no-follow、检查后替换链接 | 拒绝/不读取 | 读到外部合成内容,真实 native observer 返回 true |
| POSIX 仅缺 directory flag | AttributeError 中断 | 删除2成功,但6次请求中0次 bank/trash fsync |
这些是实际文件 IO、原 TypeScript owner、rename、receipt 和 unlink 的观察;只注入能力存在性与文件替换的调度点,没有提供伪造的 owner 返回值。native observer 使用未改的 repository fixture,不运行真实模型。这张表不是原生 Windows 或断电试验的替代。
具体改动 / Changes
七文件共 +317/-5,其中三生产文件 +35/-5;新增 AST guard、fixture、七个 runtime 用例与一个 Windows workflow 步骤。全部三提交具备 DCO。审查前先读 accepted spec_ref: docs/reference/trajectory-assessment.md,spec_revision: bd79adc649680818f44f7c5f7e5743645309fc3d;同时读同一不可变版本的 docs/reference/configuration-backup.md 与 docs/reference/research-checkpoint.md。按其原文标题逐项映射:
- Recorded Turn packet estimate:显式、本地、私有的原生诊断边界仍适用;F1 的外部 payload 读取使安全性 not_met。
- Inspect a complete backup without activating it:缺 owner 模型的 typed refusal 与不激活 Goal 得以保留,但
_regular_input的同类 fallback 竞争仍使安全读取 not_met。 - Default bank rotation:选择/私有 bank 和原 receipt 流程保留;缺属性时取消目录同步使原恢复 transport 保证 not_met,见 F2。
关键代码讲解
- read_native_trace_file 在读取前获取 flag、限制 regular file 与字节数,并由原生 context observer 消费;新增
is_symlink只检查打开前的路径,未绑定实际 descriptor。 - _regular_input 保留 owner、权限、长度检查,缺
getuid提前返回recovery_input_unavailable;缺 no-follow 时与前项存在同一窗口。 - _sync 位于 quarantine rename 与原 receipt/后续 unlink 之间,
and directory让缺 flag 的 POSIX 调用直接成功返回。 - bare_flag_uses 扫描裸属性、os alias 和 from-import,fixture 允许当前5文件16处引用下降。它与新增 Windows step 都有实际 pytest consumer;这是本地测试 vocabulary,不扩展产品协议或新增权限。
既有 loopx/file_lock.py 已使用 O_RDONLY | getattr(os, "O_DIRECTORY", 0) 后真正同步目录,是 F2 可复用的较小修复模式。_no_follow_flag 只有一个生产调用方,可考虑内联;这只是非阻塞简化建议,不能替代两项物理语义修复。
对主干的风险 / Risks
[P2] F1:关闭检查到打开之间的链接替换窗口
在缺 O_NOFOLLOW 的分支,path.is_symlink() 与 os.open() 分离。把合成 payload 在这两步之间换成 bundle 外的 symlink,当前函数读到了外部 bytes;通过真实 codex_context_in_active_input,在原有 resolve/containment 检查之后做同一替换,当前 head 返回 true。保留 native flag 的相同控制返回 false,静态 symlink 也返回 false,所以新增静态链接测试捕获不到它。恢复 _regular_input 共享此模式。请在任何读取前绑定平台安全 handle 与实际文件身份,不能建立保证时按 owner 类型化拒绝;添加这个确定性调度反例并保留正常文件用例。
[P2] F2:缺目录 flag 时不要把同步变成成功空操作
_state 仍可把该 POSIX transport 判为合格,随后 _sync 因缺 O_DIRECTORY 静默跳过。用三个真实 checkpoint、保留最新一个的策略走完整 rotate_bank(execute=True),当前 head 删除两个并返回成功;bank/trash 六个同步请求没有一次调用 fsync。正常能力的同一流程有六次,base 缺属性则中断而非报告完整成功。这里确认的是同步步骤丢失,未声称已经跑过断电或观察到数据丢失。请保留安全目录 descriptor 的 fsync,或在第一次移动前拒绝不合格 transport;不能在移走恢复点后把同步省略视为成功。
| 独立本地检查 | 结果与实际范围 |
|---|---|
| 新 portability/AST tests | PASS:head 11项;同一七个 runtime case 在 base 为6个 AttributeError FAIL、1 PASS |
| 原三个完整相关模块 | PASS:base/head 各167项,无skip;含真实 File/SQLite/HTTP 恢复、native input 和 checkpoint retry |
| 新真实入口反例 | FAIL:F1 外部 payload 被接受;F2 删除成功但目录同步为0;普通能力控制保持 |
| changed-file Ruff | PASS:五个 Python 文件 |
| configured Ruff / mypy | FAIL:两端完整697B E731、208B no-any-return 输出分别逐字相同,失败源码/config blob未改;required失败保留 |
| standard native premerge | FAIL:6 direct通过,12 selected中11通过;唯一 CLI-output differential 失败,本轮未作独立 base 归因,保持 unresolved |
| exact-scope change quality | FAIL:cqr_187826005a531df5ef88,verify invalid_receipt,没有 safe fix 或 required-check 豁免 |
语义与 CI 对齐
本次复用原 IO 与 artifact owner,不建立新的共享 state vocabulary。diff-scoped semantic advisory 未发现支持的新增生产 carrier,完整 semantic canary 通过;这不证明物理 IO 等价,F1/F2 仍是当前合同的阻塞。未查询、轮询或等待远端 CI。新增 Windows 命令只在当前 macOS 源环境执行,未宣称原生 Windows 合格;其 owner-model 与 directory 条件 skip 也不能证明对应 native 路径。
我的整体评价 / Verdict
REQUEST_CHANGES。原崩溃真实且维修边界合理,但不能批准目前的 fallback:无 no-follow 时成功读取竞态外链,无 directory flag 时成功取消持久化步骤。长程恢复保证与完整操作体验目前均为 not_yet_proven;保持正常能力的既有167项通过不等于这两个新分支安全。只改文件 IO、测试和 workflow,没有渲染页面;已检查恢复 API、原生诊断入口和 retention CLI 的既有消费者。 未运行原生 Windows、实际模型或断电恢复;能力删除模拟、真实文件 IO 与同步调用观测不能替代这些资格。
三个已有 IO owner 的放置与复用合适;缺失能力不能把安全拒绝或持久化步骤降为成功。 当前 PR 应交付这三处安全的跨平台降级与同一组守卫;修复 descriptor 身份校验和目录同步即可,无需新协议、状态或执行器。 无需新页面、协议或整套平台重写;在原分支完成两处 bounded 修复并加入实际入口负例,按新 exact head 重审。必需静态、premerge 和 CQR 红灯单独保留,不要求此 PR 顺便修改无关旧代码。没有合并或修改作者源码。
English verdict: REQUEST_CHANGES — 71d4ac4: the missing-flag fallbacks admit a raced outside symlink and silently skip checkpoint directory fsync; ordinary tests pass, but the two real-path counterexamples and required quality holds remain.
| # Without O_NOFOLLOW the open cannot refuse a symlink for us. Refuse it | ||
| # here rather than letting a diagnostic read follow an arbitrary target. | ||
| raise ValueError("native observation must not be a symlink") | ||
| descriptor = os.open(path, os.O_RDONLY | no_follow | getattr(os, "O_NONBLOCK", 0)) |
There was a problem hiding this comment.
[P2] 缺 O_NOFOLLOW 时请绑定实际打开的安全 handle
这里的 is_symlink 检查与 os.open 之间可被替换。独立测试在真实 open 前把已检查的 payload 换成 bundle 外 symlink:函数读到外部合成内容,真实 codex_context_in_active_input 返回 true;保留原生 flag 的相同控制则拒绝。静态 symlink 用例无法覆盖这个窗口,_regular_input 也有同类 fallback。请在任何读取前使用平台 no-follow/reparse-safe handle 并验证实际文件身份,不能保证时按原 owner 类型化拒绝;加入确定性替换反例。
| # O_DIRECTORY is POSIX-only, so the platform gate and the flag lookup must | ||
| # name the same thing rather than trusting `os.name` alone. | ||
| directory = getattr(os, "O_DIRECTORY", 0) | ||
| if os.name == "posix" and directory: |
There was a problem hiding this comment.
[P2] 缺 O_DIRECTORY 时不能静默省略目录同步
_state 仍会接受该 POSIX transport,而这个条件让每个同步请求直接返回。独立运行真实三 checkpoint/latest-one 轮换,去掉 O_DIRECTORY 后 head 删除两个并报告成功,bank/trash 六个请求零次 fsync;正常能力控制实际执行六次。这里不等于做过断电测试,但原持久化步骤确已丢失。可复用 file_lock 既有安全目录 fd/fsync 模式,或在首次 rename/delete 前明确拒绝不合格 transport,并用真实轮换断言同步或无副作用拒绝。
|
两处 bounded 修复已推上新的 head( F1 缺
|
| 用例 | 旧 head 71d4ac4cb |
新 head |
|---|---|---|
test_native_trace_read_refuses_a_link_swapped_in_at_the_open |
FAIL expected ValueError, nothing raised |
PASS |
test_recovery_readback_refuses_a_link_swapped_in_at_the_open |
FAIL expected StateRecoveryError, nothing raised |
PASS |
test_trace_link_swapped_after_containment_is_refused_before_the_read(经真实 codex_context_in_active_input) |
FAIL(返回 true,即接受外部内容) | PASS |
test_native_trace_read_refuses_the_same_swap_with_the_flag(原生 flag 控制) |
PASS | PASS |
最后一条走的是真实入口:fixture 不变,只在 trace.jsonl 被打开的一瞬把它换成 bundle 外的链接,旧 head 上 codex_context_in_active_input 返回 true,与你独立复现一致。
F2 缺 O_DIRECTORY 时仍然同步
_sync 不再把缺属性当成功。现在沿用 file_lock.write_json_atomic 的既有模式:os.open(path, O_RDONLY | getattr(os, "O_DIRECTORY", 0)),用 fstat 确认这个 descriptor 确实是目录(补上 O_DIRECTORY 原本提供的类型保证),再 fsync(fd);不是目录则抛 ValueError。os.name != "posix" 的早退保留,那里的 transport 本来就未合格。
真实轮换用例 test_rotation_flushes_directories_without_the_directory_flag:删掉 O_DIRECTORY,真实 checkpoint、execute=True、keep_latest=1,断言 deleted 非空且目录 fsync 至少两次(bank 与 trash 父目录)。旧 head 上该用例失败于 the directory must be flushed, not silently skipped,与该处报告同步步骤丢失一致。
本地验证范围
pytest tests/architecture/test_posix_flag_portability.py tests/control_plane/test_posix_flag_fallback.py tests/control_plane/test_codex_input_retention.py→ 107 passedruff check --select E4,E7,E9,F对本 PR 改动的五个 Python 文件 → All checks passed- 未在本机跑原生 Windows;Windows 步骤仍以 CI 为准
保持原状的项
kernel-static-checks的 E731 位于tests/test_manager_context_roundtrip.py:289,由主干acecb5d6b (#8056)引入,不在本 PR 改动内;已另开 fix(tests): spell the steward evidence sender as a def #8084 单独修,本 PR 变基后即可恢复。- CLI-output differential premerge 与 cqr 回执两条红未见与本改动相关,未在本 PR 内顺带修改。
71d4ac4 to
11691d7
Compare
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent; gpt-6.1-sol; OpenAI; runtime_reported; reasoning_effort=max.
动机 / Motivation
读取原生诊断、检查历史恢复报告与轮换 checkpoint 的操作者和 Agent。 宿主缺少 POSIX 属性时,原代码先抛 AttributeError;当前改为安全读取普通诊断或原 owner 的类型化拒绝,并保留真实目录同步。 实际文件读取与轮换已验证修复上轮两个 P2;新增 Windows 检查仍会因两项错误的平台断言失败。 不启用追踪、不执行模型、不激活备份或改变 Goal、权限、调度、配额与 checkpoint 选择策略。
本次重审完整 exact head 11691d7a8f070f5ee7add6495a3a27c6674ed4c9,不可变 merge base c6ab5afc174dfbfa2354e2c66d7431b47ac79151。目标分支已继续前进,使用真实 merge base 审阅九文件,而非把当前 main 到 head 的无关历史逆向差异算进本 PR。上轮 71d4ac4cb5cdc44c717ff163ffbf282575543d56 的两个 P2 分别映射到当前修复并重新验证,没有继承旧结论。
改动思路 / Approach
Python 保留平台 IO,既有 TypeScript owner 保留 checkpoint 选择与验证;没有平行状态或执行权威。 本 PR 的边界仍是三处安全跨平台 IO 与守卫;当前只需为两个 POSIX 同步断言补平台条件,并保留非 POSIX 的 no-op 验证。 普通缺属性输入能安全继续,比仅把异常包成错误有实际价值;直接使用 getattr 而省略安全或同步已被上轮反例否定。当前 fd 身份检查与既有 file_lock 目录同步模式解决了这两点,无需通用平台重写。下表是独立执行的行为比较,不是原生 Windows 或断电证明。
| 同源实际场景 | 不可变 base | 当前 head |
|---|---|---|
| 普通能力读取、轮换 | 正常;删除2、目录 fsync6 | 保持 |
| 缺 no-follow,普通诊断 | AttributeError / observer false | 安全读取 / observer true |
| 缺 no-follow,打开时换外链 | 不可用 | 两读取器拒绝,真实 observer false |
| POSIX 仅缺 directory flag | AttributeError,中断原轮换 | 删除2、保留最新、实际目录 fsync6 |
| 新同步测试的 nt 分支 | 本 step/test 组合不存在 | 两项失败;posix 同样调用均通过 |
具体改动 / Changes
完整九文件 +470/-9,四提交均有 DCO;最后提交增加 descriptor 与目录 entry 的身份比较、保留 fsync 及实际竞态回归。spec_ref: docs/reference/trajectory-assessment.md,spec_revision: c6ab5afc174dfbfa2354e2c66d7431b47ac79151;同时读同一版本 docs/reference/configuration-backup.md、docs/reference/research-checkpoint.md,三份文件与当前目标分支的 blob 一致。按原文标题映射:Recorded Turn packet estimate 的本地私有、不改执行权威边界 implemented;Inspect a complete backup without activating it 的私有普通文件、容量及 inert readback implemented;Default bank rotation 的既有选择、quarantine/receipt 恢复与实际同步 implemented。Windows 检查问题是新增测试与已有平台门不一致,另列 F1。
关键代码讲解
- _open_verified 在真实 open 后、读 bytes 前核对 fd 与 lstat 的 device/inode,并拒绝 symlink;同一未改 fixture 函数驱动真实 observer,正常缺标志可用、竞态外链返回 false。
- _regular_input 保留 owner、私有权限、regular 与长度检查,缺 getuid 先类型化拒绝,同样在读取前绑定身份。其 IO 异常仍由现有 CLI/API 处理。
- _sync 仅在 POSIX 验证目录 fd 后真实 fsync。三点/latest1 走完整 TS owner、rename、receipt、unlink,缺目录 flag 也实际同步六次;未改 non-POSIX 的提前返回。
- 同步 fallback 测试 与相邻“非目录必须 ValueError”用例没有 POSIX 条件,却由新增 Windows step 一起执行。这是当前剩余阻塞;AST ceiling 仍是测试本地 vocabulary,没有新产品协议。
对主干的风险 / Risks
[P2] F1:把两个目录同步断言限定到其 POSIX 合同
Windows 上 os.name == "nt",生产 _sync 按设计立即返回;但第一个新测试要求 flushed == [True],相邻测试要求 ValueError。新 Windows workflow 无条件执行整个文件,也没有全局 collection skip。独立调用未改的两个 repository 测试函数,只选择 posix/nt 分支、保留实际文件系统和真实 _sync:posix 两项通过,nt 分别报 the directory must be flushed, not silently skipped 与 DID NOT RAISE ValueError。这说明 macOS 的15项通过不能证明新 Windows 命令可用,并未冒称执行了原生 Windows。
最小修复:为这两个 POSIX 同步断言增加平台 skip,另保留 non-POSIX no-op 的明确测试,再在 Windows 执行新增的原命令。不要为满足测试而放开生产 rotation 的平台资格。上轮 F1 外链竞态和 F2 无声省略 fsync 已有新的真实反例重放证明修复,未作为本轮重复 finding。
| 独立验证 | 当前结果 |
|---|---|
| 新 portability / AST | PASS:15项,当前 macOS |
| 三个完整相关模块 | PASS:base167 / head169,无 skip;含实际 File/SQLite/HTTP 与 quarantine retry |
| 真实 IO / observer / 轮换 | PASS:外部静态/竞态链接拒绝;缺 directory 时6次 fsync,普通能力保持 |
| 精确 nt 测试分支 | FAIL:两个原函数失败;只替换 OS 分支,不伪造返回值 |
| changed Python Ruff | PASS:全部七个改动 Python 文件 |
| configured Ruff / mypy | FAIL:两端完整697B E731、208B no-any-return 分别逐字相同,失败 source/config 未改;required 红灯保留 |
| native premerge / CQR | FAIL:6 direct通过,selected11/12通过;CLI-output 未作本轮独立 base 归因;cqr_d14475ccdf57552b904c 为 fail |
语义与 CI 对齐
没有新共享 state vocabulary,diff-scoped advisory 的三生产文件未检出支持的 carrier,完整 semantic canary 通过;这些不证明平台测试正确。当前 F1 直接违反新增 Windows 检查与已有 _sync 平台门的对应关系。未查询、轮询或等待远端 CI,也未把以前或本轮未知失败改为通过。
我的整体评价 / Verdict
REQUEST_CHANGES:三处运行时维修已有正向证据,上轮两个 P2 已修复;完整当前 PR 仍需修正新增 Windows 测试的平台条件。长程恢复边界在已测本地路径中 preserved,普通诊断和拒绝体验 improved;没有据此声称实际 Host 采用或净效率改善。只修改平台 IO、pytest 与 workflow,已检查原生诊断、恢复 API 和 retention CLI;没有渲染页面变化。 未执行原生 Windows、真实模型或断电恢复;OS 分支模拟与 fsync 观测不等于这些资格。
Python 保留平台 IO,既有 TypeScript owner 保留 checkpoint 选择与验证;没有平行状态或执行权威。 本 PR 的边界仍是三处安全跨平台 IO 与守卫;当前只需为两个 POSIX 同步断言补平台条件,并保留非 POSIX 的 no-op 验证。 这是同域、可逆的修复,不需要另起实现、协议、页面或任务。strict CQR、已有静态失败、CLI-output 失败与后续 merge-readiness 保留各自门;未修改作者源码或合并。
English verdict: REQUEST_CHANGES — 11691d7: the earlier link-race and missing-fsync defects are fixed, but the new Windows step runs two unguarded POSIX-only sync assertions that fail on the Windows branch.
| pass | ||
|
|
||
|
|
||
| def test_checkpoint_sync_flushes_the_directory_without_the_flag(tmp_path, monkeypatch): |
There was a problem hiding this comment.
[P2] 请把这两个目录同步断言限定到 POSIX
新增 Windows step 会无条件运行本文件,但 Windows 的 os.name 为 nt,_sync 在现有平台门直接返回。这里因此拿到空 flushed;相邻“非目录必须 ValueError”测试也不会抛错。独立调用未改的两个测试函数、只选择 posix/nt 分支:posix 均通过,nt 分别触发此断言与 DID NOT RAISE ValueError。请将两个 POSIX 断言标记为 POSIX-only,另保留 non-POSIX no-op 用例,并运行原生 Windows 命令;不要放开生产平台资格来迁就测试。
O_NOFOLLOW, O_DIRECTORY and os.getuid are POSIX-only attributes. Spelling one directly raises AttributeError on a host that does not define it, and it raises before the bounded private regular-file check on the next line can refuse anything, so each of these readers reported an untyped crash in place of its own refusal: - read_native_trace_file is reachable on any host and had no gate, so the observation it exists to produce never appeared. - the recovery readback is reachable without the platform gate verify_state_backup applies, and its os.getuid() failure came after the open had already succeeded. - checkpoint_retention._sync already tested os.name == "posix", but never asked whether the host that satisfies that test also defines O_DIRECTORY. Read the flag this host has, as the peer, completion, decision-capture and effect-runtime readers already do, and refuse a symlink explicitly where the open cannot. The recovery reader reports its own recovery_input_unavailable instead of letting AttributeError escape. Signed-off-by: ChengLong GU <88359731+gcl-coder@users.noreply.github.com>
A bare os.O_NOFOLLOW, os.O_DIRECTORY or os.O_NONBLOCK raises AttributeError on a host that does not define it, and it raises before the bounded regular-file check on the next line can refuse anything. The repository already reads these flags with getattr in the peer, completion, decision-capture and effect-runtime readers. Pin every remaining site per file so a new one needs a recorded reason, and assert the readers this change fixed stay at zero. A lower measurement is accepted, so retiring one of these sites does not have to edit a fixture that would then describe a tree it no longer matches. Signed-off-by: ChengLong GU <88359731+gcl-coder@users.noreply.github.com>
Deleting os.O_NOFOLLOW, os.O_NONBLOCK, os.O_DIRECTORY or os.getuid for the duration of one call is the branch a host that lacks them takes. Six of these seven cases failed on the pre-fix tree with `AttributeError: module 'os' has no attribute 'O_NOFOLLOW'`, which is the same failure a native Windows host reports, so the cases reproduce the defect on a POSIX checkout. The Windows lane runs the guard and these cases as well, so the branch a real Windows host takes stays qualified in the place it is reached. Signed-off-by: ChengLong GU <88359731+gcl-coder@users.noreply.github.com>
Two holds were open in the previous fallback. Reading without O_NOFOLLOW checked the path and then opened it, so the entry could be replaced by a link to another file in between: nothing was bound to what was actually opened. Open first, then compare the opened descriptor with the directory entry before one byte is read. A link opens to its target, whose device and inode differ from the link's own, so a swapped or linked entry is refused instead of followed. The native trace reader and the recovery readback share the rule. checkpoint retention treated a missing O_DIRECTORY as permission to skip the flush, so a POSIX host without the flag deleted recovery points and reported a rotation that never became durable. O_DIRECTORY only hardens the open: take the flag the host has, refuse a descriptor that is not the directory, and flush it, as file_lock.write_json_atomic already does. Static-link cases could not see either defect. The new cases replace the entry at the instant it is opened, and one runs through codex_context_in_active_input, so head before this commit fails them. Signed-off-by: ChengLong GU <88359731+gcl-coder@users.noreply.github.com>
The new native Windows step ran the whole fallback file, but two sync assertions describe a POSIX bank: _sync returns before flushing when the host is not POSIX, so the flush list stayed empty and the non-directory target raised nothing. Mark those two POSIX-only and pin the other branch with an explicit no-op case that holds on both hosts. Signed-off-by: ChengLong GU <88359731+gcl-coder@users.noreply.github.com>
|
F1 已修,只动测试,没碰生产平台的资格判断。
本地(macOS/posix): 以上是 macOS 上的结果,不代替原生 Windows 证据;那条要等这个 head 的 windows-powershell lane 跑完。 |
11691d7 to
99e9b5d
Compare
loopx-agent
left a comment
There was a problem hiding this comment.
No blocking findings. 本次重新检查完整 exact head,未继承旧结论。
Reviewer: model_agent; model=gpt-6.1-sol; provider=OpenAI; reasoning_effort=max; declaration_source=runtime_reported; execution_observation_id=32dd9a2462f888f9986adfde40a0273a4fd158866b2a5abcd0acb166065f0a17.
动机 / Motivation
在缺少部分 POSIX 文件标志的主机上读取本地诊断、恢复报告或维护检查点的用户会遇到这个问题。 以前,读取一个合法本地文件可能在安全检查前直接抛出 AttributeError;现在普通文件可读,换入的符号链接被拒绝,合格 POSIX 目录仍完成刷新。 本次对照验证了缺失标志分支的可用性和拒绝行为,并确认此前两项 IO 问题及 Windows 测试条件问题已在当前 head 修复。 本 PR 不扩大备份激活、检查点删除权限或诊断启用范围,也不证明实际 Windows 运行资格、断电耐久性或模型收益。
改动思路 / Approach
文件安全和耐久性是既有 IO 不变量,必须在原 reader/transport 内成立;不是新的可选策略,也不产生第二个决策 owner。 仅三个既有 Python IO 入口、回归用例、局部静态防退化上限和 Windows 测试步骤;保留 TS 策略与平台门。 入口仍是原生诊断读取、隔离恢复和显式轮换;打开文件后的实际 descriptor 元数据与路径 entry 进行设备号和 inode 核对,再执行原有 regular/private/size 检查。这样既保留正常读取,也拒绝换入的链接。只有既有平台资格允许的轮换才能到达目录刷新;能力存在本身不会启动诊断或删除任务。
两个 reader 的错误类型、owner 条件和资源生命周期不同,局部复用 os/stat 模式比抽出可配置安全文件框架更窄。新增 _open_verified 与 _require_directory 是相邻职责内的有限整理,未复制 TypeScript 策略。下表仅解释源码行为,不作为执行证明;本次没有新增图形或 Mermaid 渲染要求。
| 触发条件 | 旧路径 | 当前 head |
|---|---|---|
| 主机缺失文件 flag | 属性访问先失败 | 探测 flag 后继续既有资格检查 |
| 缺失 no-follow 且 open 时换入链接 | 旧 fallback 曾存在读取窗口 | descriptor/entry 绑定,读取前拒绝 |
| POSIX 缺失 directory flag | 旧实现失败或旧 fallback 跳过刷新 | 证明目录类型后仍 fsync |
| 非 POSIX | 不声称目录刷新 | 保持无操作 |
具体改动 / Changes
评审范围是 e7904f97cdec03888c4b0f376f03c33d51d5182c...99e9b5d5ed5f0a9ce7b9ce206aa6a16888198041:9 个路径,487 行增加、9 行删除。生产代码仅三个文件;其余为 Windows workflow、一个本地 AST 债务上限和持久回归。该测试会扫描 os alias 和 from-import,不依据业务描述文字分配权限;记录的 16 个既有 bare sites 没有在本 PR 中扩大,降低计数不需要把旧上限重新抬高。
关键代码讲解
-
_open_verified:请求主机具备的 flags;读取前绑定 descriptor 与当前非链接 entry。输入或前态为 缺失 O_NOFOLLOW/O_NONBLOCK、换入链接;结果由 read_native_trace_file 消费,失败由 ValueError/OSError 并关闭 descriptor 处理。
-
_regular_input:无 owner model 时拒绝;保留私有 regular-file 和容量检查。输入或前态为 缺失 getuid/no-follow、链接、非私有或超界;结果由 恢复 audit/verify 消费,失败由 StateRecoveryError;失败关闭 fd 处理。
-
_sync:非 POSIX 明确无操作;POSIX 缺少 O_DIRECTORY 时仍检查目录并 fsync。输入或前态为 合格 bank 已进入轮换;结果由 真实 rotation receipt 消费,失败由 异常阻止成功回执,finally 关闭 fd 处理。
规格与验收映射
先读取改动前接受的文档:docs/reference/trajectory-assessment.md; docs/reference/configuration-backup.md; docs/reference/research-checkpoint.md;spec_revision=e7904f97cdec03888c4b0f376f03c33d51d5182c。这里依据既有局部 IO 契约判断,不要求这项兼容修复完成完整恢复或研究平台路线图。
trace-private-bound— implemented:Native tracing remains local, opt-in and private, with no result, authority or model-execution change. 对应loopx/control_plane/turn_driver/codex_input_trace.py;验证:185 tests plus independent missing-flag/swap probe。recovery-inert-input— implemented:Source archive and manifest must be private regular files; only a new inert checkpoint may be published. 对应loopx/control_plane/state_recovery_io.py;验证:real File/SQLite recovery suite and recovery no-follow probe。retention-posix-durable— implemented:Execution requires a private operator-owned bank on the qualified POSIX transport; recovery and retained points remain protected. 对应loopx/hosts/checkpoint_retention.py;验证:real rotation, pin/recovery negatives, missing-directory-flag fsync and nt no-op。
此前评审的两个 runtime 问题和 11691d7a8f070f5ee7add6495a3a27c6674ed4c9 的 Windows 测试问题分别复核:当前 head 给两项 POSIX-only 刷新断言加平台条件,并另测 nt 无操作;生产 _sync 平台门没有放宽。不是简单删除失败断言来获得通过。
对主干的风险 / Risks
最强风险仍是无 flag 时跟随链接、丢失目录刷新或绕过私有/容量条件。本次独立的真实文件探针在固定 base 复现 AttributeError,在 head 验证原字节读取、换入链接拒绝、真实目录 fsync 与 nt 无调用;185 项相关测试覆盖真实 File/SQLite 隔离恢复、完整轮换、pins、未知文件及 quarantine 恢复。固定 base 的原有集合为 167 passed;新增回归与完整现有行为一并验证,不能只用新增测试数量证明安全。
TypeScript typecheck、仓库声明范围 Ruff、mypy(17 sources)及 standard premerge 通过,预合并报告保留一个继承的 maintainability advisory;没有调高上限或豁免必需检查。最初误写测试路径的一次调用未执行任何测试,已改为真实 tests/test_state_recovery.py 路径后完成上述集合。没有查询或等待远端 CI。原生 Windows、真实断电和最新 main 集成后的运行没有在本地重新资格化;本次结果不能替代它们。
语义与 CI 对齐
复用原平台与传输语义;新增 ceiling schema 是 local_only 测试词汇。diff-scoped semantic advisory 未识别新的生产闭集,但这不等于自动证明所有语义。新的测试修复提示没有移除原私有、容量、权限、停止或启用条件;actual no-flag/nt 反事实与源码分别核对。诊断关闭、恢复未调用和保留未启用时没有新增文件状态、计时器、模型调用或 quota 行为。实际 Windows CI 仍由其原 runner 判定。
我的整体评价 / Verdict
交付判断 goal_achieved 仅限本次可移植 IO 修复。long_horizon=preserved;user_experience=improved:合法文件不再因属性缺失先崩溃,非法输入仍沿有界资格拒绝。已检查三个内部文件传输路径和 Windows CI 步骤;没有渲染页面、布局或可视交互变更。 代码体量与该安全边界相称,现有 owner 和局部 helper 足够,无需另建框架。严格 change-quality 记录与验证的最终结果单独保存;此前失败记录没有被改写成通过。
原生 Windows、真实断电、模型采用和长期成本收益仍未知;这些限制与本地已验证结论分开。该 exact-head 评审不授予 merge、review dismissal、安装或发布权限。
English verdict: APPROVE — no blocking finding remains at 99e9b5d; native Windows and power-loss qualification remain unverified.
问题
os.O_NOFOLLOW、os.O_DIRECTORY、os.O_NONBLOCK和os.getuid都是 POSIX 属性。在不定义该属性的宿主上直接引用,会在下一行的"有界私有普通文件"校验之前抛出AttributeError,调用方拿到的是无类型的崩溃,而不是它自己的拒绝,或者它本该产出的诊断。三个读取处都受影响:
read_native_trace_fileread_state_recovery_audit_physical_ancestors不做平台判定(verify_state_backup才做),所以恢复回读先在os.open抛AttributeError;即便绕过,紧随其后的os.getuid()也一样checkpoint_retention._syncos.name == "posix",但没确认满足这一条件的宿主一定定义O_DIRECTORY证据
同仓库已有正确写法,
loopx/下 21 个文件 30 处用getattr(os, "<标志>", 0)取这些属性(file_lock.py、completion_result.py、decision_capture.py、effect_runtime.py等)。在 POSIX 上删掉该属性即可复现同一分支:改动前新增的 7 个用例有 6 个失败,报错就是
AttributeError: module 'os' has no attribute 'O_NOFOLLOW',与原生 Windows 上的表现一致。改动
recovery_input_unavailable,AttributeError不再逃逸;O_NOFOLLOW时显式拒绝符号链接,因为这时os.open无法替我们拒绝,诊断读取不应跟随任意目标。_sync把平台门控和取标志放在同一处,两者不再各自表述。os.getuid的存在性,宿主没有所有者模型时给出自己的错误码,而不是等两行之后抛AttributeError。守卫
新增
tests/architecture/test_posix_flag_portability.py:用 AST 扫描loopx/**/*.py,识别os.<标志>、import os as x别名、from os import <标志>三种裸用;getattr里的字符串不计入。仍裸用的文件在
posix_flag_ceilings.json里按文件记录上限(当前 5 个文件共 16 处),新增即失败;上限只降不升,降到更低不会被拒,这样正在改这些文件的 PR 不会因为修好了而被这条守卫挡回来。测试
tests/control_plane/test_posix_flag_fallback.py:7 个用例,覆盖缺O_NOFOLLOW、O_NONBLOCK、O_DIRECTORY、getuid时的正常读取、拒绝符号链接、以及_sync跳过与其正向路径;改动前 6 个失败。windows-powershell新增一步,运行上面的守卫与这 7 个用例,让真实 Windows 分支在它发生的地方被验证。范围:3 个读取处、1 个守卫、1 组用例、1 个 Windows lane 步骤。