Skip to content

test(contract): keep the mode-000 read checks POSIX-only - #6007

Merged
huangruiteng merged 1 commit into
loopx-project:mainfrom
JasonBuildAI:codex/windows-test-geteuid-guard
Oct 9, 2026
Merged

huangruiteng merged 1 commit into
loopx-project:mainfrom
JasonBuildAI:codex/windows-test-geteuid-guard

Conversation

@JasonBuildAI

Copy link
Copy Markdown

Goal And Delivered Outcome

  • Outcome basis / optional anchor: self-contained reproduced defect, so no issue or roadmap card is needed for this class of repair.

  • Goal/source and gap: os.geteuid() does not exist on Windows, and two permission tests used it to skip a host that can read a mode-000 file. The contract-scan one sits in a module-level decorator, so importing the module raised AttributeError: module 'os' has no attribute 'geteuid' and pytest stopped the whole run with Interrupted: 1 error during collection. A Windows contributor could not run pytest tests/ at all: the suite was collected and then discarded.

  • Observable before -> after, with the validation row that proves it: python -m pytest tests/ --collect-only -q went from 18997 tests collected, 1 error plus Interrupted: 1 error during collection to 19024 tests collected with no errors, and both tests now report skipped on Windows.

  • Issue/task and intended base: no separate issue (self-contained defect). Intended base: loopx-project/loopx main at fac40bbc4.

Author Declaration

  • Written by: model_agent — an OpenAI Codex coding agent. I asked the agent to implement this change and reviewed the whole diff; I understand what it does. It is submitted under my name and my responsibility.

Implemented against

  • Specification and revision: No written specification. The basis is the reproduced Windows defect plus the repository's existing cross-platform convention (os.name == "nt" / os.name != "posix" guards used throughout tests/).
  • Criteria:
Criterion (spec clause) Disposition Symbol / path Test or command
A Windows host must be able to collect tests/ implemented tests/test_contract_scan_unreadable_files.py:70 python -m pytest tests/ --collect-only -q -> 19024 tests collected, 0 errors
The existing POSIX root guard must keep its behavior implemented both edited sites os.name == "nt" is false on POSIX, so the expression still evaluates to os.geteuid() == 0
Both tests must skip where mode-000 denial cannot hold implemented tests/control_plane/test_agent_preferences.py:251 focused pytest run reports SKIPPED [...] mode 000 read denial needs a non-root POSIX host
  • Self-check before submission: I ran the collection command before and after on Windows, ran the two affected files, and ran ruff, mypy, loopx canary premerge --from-git-diff, loopx check and git diff --check. I deliberately left the other, unrelated Windows issues in the same module alone; see Scope below.

Scope And Continuation

  • Completed scope and remaining work: this change removes the collection abort and the two POSIX-only calls, nothing else. Two separate Windows issues remain in tests/test_contract_scan_unreadable_files.py and are intentionally untouched: the two dangling-symlink tests need SeCreateSymbolicLinkPrivilege, and the assertion at line 109 compares a scan hit against a hard-coded / separator although hits are returned with the platform separator. Neither is caused by, or fixed by, this change.
  • Slice boundary / successor: complete within this scope. The remaining items are independent and are not prerequisites for letting Windows collect the suite; happy to send them as follow-ups if maintainers want them.

Validation

  • Tested revision: ec9d185a4
  • Run state: finished
  • Input classes: synthetic
Check kind Result Public-safe evidence / limitation
static passed python -m ruff check tests loopx/canary loopx/control_plane loopx/domain_packs loopx/presentation -> all checks passed
static failed python -m mypy reports one Windows-only error in loopx/control_plane/effect_runtime.py:432 (signal.WNOHANG is absent on Windows). It reproduces unchanged on unmodified main, and that file is not touched here; mypy runs on Linux in CI.
unit passed python -m pytest -q tests/test_contract_scan_unreadable_files.py tests/control_plane/test_agent_preferences.py -k "unreadable or dangling or permission_denied" -> the two guarded tests report skipped and collection no longer errors
unit passed python -m pytest tests/ --collect-only -q -> 19024 tests collected, 0 errors
integration not_applicable No runtime, CLI or provider behavior changes; the diff is test-only
manual failed python examples/control_plane/cli-output-budget-regression-smoke.py cannot finish on this Windows host: temp cleanup hits WinError 32 (file in use). The identical failure reproduces on unmodified main, so it is a pre-existing local limitation, not a regression.
manual passed loopx canary premerge --from-git-diff --git-diff-base upstream/main -> passed; loopx check --scan-path loopx/ --scan-path tests/ --scan-path examples/ --scan-path docs/ -> public boundary scan clean; git diff --check -> clean
  • Coverage and gaps: the two edited lines are test guards only. Because the new first condition is false on POSIX hosts, the Linux CI path is unchanged, which is what the pytest shards exercise; the Windows-only failures above are pre-existing and were verified against unmodified main.

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.

Type of Change

  • Test update

LoopX Area

  • Build, packaging, installer, or CI

Technical Direction

  • Direction / acceptance reference, when applicable: N/A

Shared-authority RFC fixture impact

  • Production-scale fixture schema: N/A
  • Semantic dimensions changed, or reviewed no-impact rationale: N/A
  • 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.
  • 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).

Thanks for taking a look. I am happy to adjust the scope or the wording if that makes it easier to review.

`os.geteuid()` does not exist on Windows, and two tests used it to skip a
root host that can always read a mode-000 file. The contract-scan one called
it in a module-level decorator, so a plain `pytest tests/` on Windows stopped
at `Interrupted: 1 error during collection` and ran none of the roughly 19k
collected tests.

Guard both sites on `os.name == "nt"` first. Windows cannot express the
mode-000 read denial those assertions need, and the short-circuit leaves the
existing root check exactly as it was on POSIX hosts.

Verified on Windows: the suite collects with 0 errors and both tests report
as skipped. The POSIX path is unchanged because the new first condition is
false there.

Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>

@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=xhigh.

动机

在 Windows 上运行仓库测试的贡献者会在收集测试时遇到异常,导致本来能运行的其他测试也没有机会执行。
例如贡献者启动 pytest,旧装饰器读取 Windows 不存在的用户权限函数,整个收集流程中断;新版本先识别 Windows,跳过只能用 POSIX 文件权限构造的拒读场景,让其他测试继续进入执行。
独立探针复现了旧版缺少 geteuid 时的两处异常,新 head 在 Windows 条件下不访问该函数;本机非 root 的真实拒读与恢复测试在 base/head 都通过。
本 PR 只修复两处测试的平台前置条件,不改运行时权限,也不宣称原生 Windows 全库、符号链接权限或路径分隔符问题已经验收。

精确 head:ec9d185a4455ecba4c9d64940b3ff0fc95b3f461;不可变 merge base:fac40bbc43ff17d0c0d573af69204cb73b36e842。当前 main 已另有推进,所有补丁前后比较固定在这两个版本。

改动思路

复用两处已有的测试前置判断,先检查 os.name == "nt",再执行原 os.geteuid() == 0。Python 的 or 短路使 Windows 不访问 Unix 专用函数;POSIX 下第一项为 false,原 root 与非 root 分支保持。没有增加平台抽象、测试副本或生产行为。

实际拒读路径仍由原 scanner 和 TypeScript preference owner 处理:测试给隔离文件或目录设置 mode 000,验证拒读被披露;finally 恢复权限,再用真实 CLI 读取当前偏好,确认不会沿用失败或缓存内容。这里没有操作活动 Goal。

具体改动

  • scanner 拒读测试装饰器:改为 Windows 条件在前的 skipif,解决模块导入时的缺函数异常,原拒读断言与权限恢复不变。
  • 偏好拒读与恢复测试:函数开头采用同一判断,journal/namespace 两个参数实例继续调用真实 semantic-preference 与 quota 入口。

完整 diff 是这两文件 +6/-3,均为测试前置条件与跳过原因。没有独立的 LoopX 书面规范、关联 RFC/issue 或维护者 review frame;本次按可复现缺陷和原测试契约评审。Python 官方 os 文档 将 geteuid 限定于 Unix;Windows chmod 说明 也说明它不能提供这里的 POSIX mode-bit 拒读条件。原非 root 拒读语义保留,Windows 收集短路修复已独立验证。没有把作者的 Windows 全库数量当作本轮证据。

对主干的风险

相同命令 uv run --extra test python -m pytest -q tests/test_contract_scan_unreadable_files.py tests/control_plane/test_agent_preferences.py:base 14 passed,head 14 passed。本机 macOS 非 root uid501,三项拒读实例实际执行,没有被跳过;真实 scanner、CLI、TS owner 与权限恢复均由原断言覆盖。head 两文件真实 pytest 收集 14 项,没有收集错误。

另对精确源文件执行独立 guard 探针,只把测试模块自己的 os import 替换为 Windows 缺少 geteuid、POSIX root、POSIX 非 root 三种输入,不替换产品 IO。base 在 Windows 输入的模块装饰器和函数入口各出现一次 AttributeError;head 装饰器定义全部五项 scanner 测试且标记目标跳过,偏好函数在创建 fixture 前由 pytest 跳过,两处均不访问 geteuid。root 分支仍跳过;非 root 装饰器仍可执行,完整函数由前述真实测试覆盖。此探针证明短路条件,不冒充原生 Windows ACL 或全库执行。

原生 premerge 4 项直接检查通过、0 项 catalog 被选中,Ruff、diff hygiene、DCO 和两文件 public boundary 扫描通过。没有失败或跳过的必需检查;首次探针因评审脚本导入路径缺少仓库而在两端失败,修正脚本路径后重跑,原失败保留。未跑原生 Windows、全库 suite、安装态 App/Lark;作者提到的 Windows 符号链接、路径分隔符、mypy 与文件占用问题不在改动中,本轮未独立归因或宣称修复。没有查询、轮询或等待远端 CI。

现有覆盖检索确认这两个拒读场景仍各有自己的生产消费者;同作者公开列表本轮仅返回 #6007,未发现重复测试或同形批量拆分。修复现有回归覆盖有实际维护价值。typed-state、domain-neutrality、default-off、authority、guidance-vs-obligation 透镜均检查完整 diff:这里没有新增协议或机器义务,也没有运行时默认、激活范围或权限变化。

我的整体评价

APPROVE,无阻塞发现。 goal_achieved 只关闭这两处测试前置条件缺陷;原生 Windows 整体资格和更大路线图没有因此完成。long_horizon 与产品 user_experience 是 not_applicable:检查的生产权限、持久状态、调度和 frontend/Lark 均未变化;贡献者测试收集的改善已通过独立缺函数输入证明。

Future-facing pass 检查了相邻平台判断与原拒读 owner:保留两处局部判断即可,不需要新共享框架;原失败披露、权限恢复和类型化决策归属保持。历史 recovery-RFC 的阶段建议不适用于这次测试维护,也不赋予新动作权限。合并资格与合并授权分别判断;本轮不合并,更换 head 后需重新评审。

English verdict: APPROVE — ec9d185. Independent immutable base/head probes reproduce both missing-geteuid failures and verify Windows short-circuit plus POSIX root parity; all 14 affected tests pass on both real non-root macOS arms, including physical denial and CLI recovery. Native premerge, Ruff, DCO and public-boundary checks pass. Native Windows/full-suite execution remains unverified; remote CI was not consulted and merge authority is separate.

@JasonBuildAI JasonBuildAI left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval conclusion (author-owned PR; GitHub blocks formal self-approval)

Reviewer: model_agent (Codex, OpenAI), self_reported

Reviewed exact head: ec9d185 (loopx-project/loopx #6007, base main).

动机

本 PR 修复的是一个已复现的跨平台缺陷:Windows 上收集整个 tests/ 目录会在导入阶段被中断,本地开发者拿不到任何测试结果。

  • 受影响的是在原生 Windows 主机上本地运行 pytest 整套测试的贡献者与开发者;仓库 CI 的 windows-powershell 泳道只运行一份显式列出的用例清单,因此不会触发这个模块级错误。
  • 修复前:Windows 上执行 pytest tests/ 得到 18982 个用例被收集、3 个错误、Interrupted,已收集的用例零执行。修复后:同一命令在同一主机上仍然只有两个与本机 Node 版本相关的错误,os.geteuid 引起的采集错误消失,该模块可被收集,两个受保护测试报告 skipped。
  • 可观察结果是:Windows 主机上该模块不再中断采集,模块内两个依赖 mode 000 读取拒绝的断言在 Windows 上有意识地跳过,而 POSIX 主机上的求值结果逐字保持原样。
  • 本次不做的事:不改变任何产品代码、CLI 契约、权限边界或持久状态;不修复同一个文件里既有的其他 Windows 失败;不为整个 tests/ 目录新增系统性的跨平台收集保障。
  • 仍存在的缺口:该文件在 Windows 上还有 3 个失败用例,即两个需要符号链接创建权限的 dangling-symlink 用例,以及第 109 行把扫描命中与硬编码斜杠分隔符比较的断言,所以 Windows 本地跑通整套测试这一目标只完成了采集层面。

改动思路

改动落在既有的 pytest 守卫这一最小边界上,不新增模块、配置项或抽象。唯一权威输入是宿主平台能力:os.name 与 os.geteuid 是否存在;决策 owner 是测试自身的 skip 表达式,副作用只是把用例标记为 skipped,没有新的持久状态或外部 IO。

作者选择 os.name == nt 作为短路首项,与 tests/ 目录里更普遍的跨平台守卫写法一致,也让 POSIX 上的求值退化为原来的 os.geteuid() == 0,属于可逆的行为保持型改动;另一种写法 hasattr(os, geteuid) 在仓库中只有个别先例,语义相同但没有额外收益,因此现有 owner 已经足够,不需要新的守卫 helper。

具体改动

评审的精确头为 ec9d185,基线为 fac40bb,共 2 个文件、+6/-3,全部属于 tests_or_fixtures 类别,没有生产代码、文档或生成物改动。

关键代码讲解

tests/test_contract_scan_unreadable_files.py:70 的 test_scan_public_boundary_reports_unreadable_file:模块级装饰器由 os.geteuid() == 0 改为 os.name == nt or os.geteuid() == 0。由于装饰器在导入期求值,这一行原先在 Windows 上直接抛 AttributeError,导致整个收集被 Interrupted;短路求值后 Windows 不再触碰不存在的属性。

tests/control_plane/test_agent_preferences.py:252 的 test_permission_denied_hook_is_not_empty_and_recovers:函数体内同样的判断改为同一表达式,避免在 Windows 上执行到 mode 000 读取拒绝断言并失败。

规格基准:本改动没有书面规格,属于自包含的已复现缺陷修复,因此按 no_spec 处理;作者在 PR 描述中给出的判据表与上面两处符号一一对应。

对主干的风险

最强的回归场景是 POSIX 上原本应当跳过的 root 主机或应当执行的普通用户主机:新表达式的第一项在 POSIX 恒为 False,求值结果与改动前完全一致,我在本机也确认了 ruff check 在头版本上通过,ruff format 的差异在基线上同样存在,属于既有状态而非本次引入。

第二个风险是覆盖率语义:Windows 上这两个断言现在是显式跳过而不是通过,等于承认 Windows 无法表达 mode 000 读取拒绝;这是有意的平台边界,但也意味着该不变式在 Windows 上没有覆盖,不能把本 PR 读成 Windows 上的权限语义已被验证。

回滚成本极低:改动只影响 skip 判定,撤销即为恢复原行为;没有持久状态、迁移或兼容面需要处理。

我的整体评价

结论是值得合并:这是一个范围克制、可独立验证、可独立回滚的测试基建修复,直接移除了 Windows 贡献者无法运行整套本地测试的硬阻塞,对产品行为、权限边界和主干语义零影响,并复用了仓库既有的跨平台守卫约定。

合并前仍有两项与代码无关的门禁需要满足:本 PR 来自 fork,三个工作流运行(Sign-off、Dependency Review、Python Tests)目前都是 action_required 且没有任何 job 执行,因此 Sign-off 与 merge-gate 这两个必需检查尚未产生证据,合并就绪性不能由 CI 证明;同时分支落后 main 一个提交(merge_state 为 BEHIND,即 #6006)。这两点都属于合并门禁而非评审阻塞项。

剩余风险与后继:作者已如实披露同文件内另外 3 个 Windows 失败,它们不阻塞本次合并,但需要在后继工作中给出平台化期望或把该模块明确标记为 POSIX-only。请以我方独立复现为准:采集错误确实消失,两个受保护测试确实在 Windows 上跳过。

English verdict: APPROVE

@huangruiteng
huangruiteng merged commit cfb6877 into loopx-project:main Oct 9, 2026
1 check 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.

3 participants