cua-driver 的 Linux Nix 测试布局:可复现构建、NixOS VM 策略校验与单一行为事实源
【免费下载链接】cuaScale computer-use 2.0 with open-source drivers, cross-OS fleets, and benchmarks for training, evaluation, and data generation.项目地址: https://gitcode.com/GitHub_Trending/cua/cua
本篇技术指南聚焦开源仓库 cua 中 nix/cua-driver/tests/README.md 所定义的Linux Nix 检查(check)布局:它描述了 Nix 表达式按“各自证明什么”分组的三个测试入口(rust-unit.nix、policy-yaml.nix、policy-rego.nix),并明确了 Nix 在测试栈中的职责边界。读完本文,你将理解 cua-driver 的 Rust 二进制如何在 Nix 可复现环境中完成无桌面会话的单元/编译检查,如何在 NixOS 虚拟机中通过 stdio 的 MCP JSON-RPC 校验 YAML 与内嵌 Rego 权限策略的强制执行,以及为什么“桌面行为目录”刻意不出现在 Nix 中、而是统一由类型化 Rust 测试框架作为唯一事实源。
一、背景:Nix 在 cua-driver 测试栈中的两种职责
cua-driver 是仓库中基于 Rust 实现的跨平台 MCP 服务器(cua-driver二进制通过 stdio 提供 40+ 个工具,覆盖屏幕捕获、鼠标键盘输入、窗口管理与基于无障碍能力的元素交互,见 package.nix)。在它的测试栈中,Nix 承担两种职责(见 nix/cua-driver/README.md):
- 可复现的 Linux 构建:从锁定的 Rust 源码构建发布用 Linux 包;
- 桌面会话依赖供给:为“权威的”Rust 类型化测试框架提供桌面会话与运行环境。
这两条主线决定了 tests 目录下的文件如何组织。
二、检查布局总览:每个 Nix 表达式“证明什么”
原文档以一张路径-角色表给出了 tests 目录的分工,这是整个布局的核心骨架:
| Path | Role |
|---|---|
rust-unit.nix | Source-built Rust workspace checks without a desktop session |
policy-yaml.nix | NixOS VM stdio checks for YAML allow, deny, and argument constraints |
policy-rego.nix | NixOS VM stdio checks for embedded Rego policy evaluation |
三个文件均位于 nix/cua-driver/tests/,并在仓库根 flake.nix 的checks属性集中注册为三个 flake check:
checks = { cua-compositor-build = cuaCompositorPackage; cua-driver-build = cuaDriverPackage; cua-driver-linux-rust-unit = import ./nix/cua-driver/tests/rust-unit.nix { inherit pkgs; src = rustTestSrc; sourceSubdir = "rust"; }; cua-driver-policy-yaml = import ./nix/cua-driver/tests/policy-yaml.nix { inherit pkgs; cuaDriver = cuaDriverPackage; }; cua-driver-policy-rego = import ./nix/cua-driver/tests/policy-rego.nix { inherit pkgs; cuaDriver = cuaDriverPackage; }; };flake 针对x86_64-linux与aarch64-linux两个系统各构建一套,因此一次nix flake check即可覆盖两个架构的上述全部检查。
三、rust-unit.nix:无桌面会话的源码级 Rust 工作区检查
rust-unit.nix 的目标是:在 Nix 的可复现构建环境中验证 Rust 源码与测试工具链本身,而不依赖任何正在运行的桌面会话。
3.1 与package.nix刻意分离
文件头注释明确了两者分离的原因:发布用的 daemon 包(package.nix)必须保持与显示器无关(display-independent,doCheck = false),而这个 check 则专门负责在 Nix 构建环境里运行测试。两者读版本的方式一致——都从Cargo.toml的workspace.package.version通过pkgs.lib.importTOML读取单一事实源,避免版本号漂移。
3.2 关键实现细节
- 源码子目录:通过
sourceSubdir(默认null)与postUnpack把 flake 传入的rustTestSrc定位到rust子目录,Cargo.lock 同样取自${rustSrc}/Cargo.lock,保证依赖完全锁定; - 测试范围:
cargoTestFlags覆盖cua-driver、cua-driver-core、cua-driver-testkit、platform-linux四个 crate,并附带--all-targets(同时构建与运行所有目标,含集成测试); - 依赖注入:
nativeBuildInputs需要pkg-config、rustPlatform.bindgenHook与clang(供 bindgen 生成 libpipewire/libspa 的 SPA pod 代码);buildInputs提供 X11 栈(libx11/libxi/libxtst/libxext)以及 Wayland 对等能力所需的pipewire与libei; - 默认测试集是“无头”的:注释强调,默认 Cargo 测试集合刻意不启动桌面;被 ignore 的桌面矩阵由手动 Linux e2e 工作流运行,而不是隐藏在 Nix 中。
这与package.nix中“默认关闭测试、只构建二进制”的定位形成互补:包要轻量可发布,check 要尽量充分验证源码。
四、policy-yaml.nix:在 NixOS VM 中校验 YAML 权限策略
policy-yaml.nix 使用pkgs.testers.runNixOSTest启动一台 NixOS 虚拟机,在 VM 内完成MCP stdio(JSON-RPC 2.0 over stdio)的端到端权限校验。
4.1 测试策略文件
VM 内的测试命令首先写入/tmp/policy.yaml:
allow: tools: - screenshot rules: - tool: click constraints: x: { min: 0, max: 1920 } y: { min: 0, max: 1080 } deny: tools: - type_text这份 YAML 演示了策略文件的核心语法:allow.tools是无条件放行的工具列表,allow.rules是带参数约束的条件放行规则,deny.tools是显式拒绝的工具列表。
4.2 启动 daemon 并等待就绪
以CUA_DRIVER_POLICY_FILE环境变量指向策略文件,启动cua-driver serve,并轮询cua-driver status --socket(最多 200 次、每次间隔 0.05s)确认 socket 就绪后才开始发请求:
env \ CUA_DRIVER_POLICY_FILE=/tmp/policy.yaml \ CUA_DRIVER_RS_TELEMETRY_ENABLED=false \ cua-driver serve --socket "$socket" --no-permissions-gate --no-overlay \ >/tmp/daemon.log 2>&1 &注意此处同时关闭了 telemetry、permissions-gate 与 overlay,让测试聚焦于策略引擎本身。
4.3 通过 stdio MCP 断言四类行为
测试通过 coproc 建立cua-driver mcp --socket子进程,用printf写入 JSON-RPC 请求、read读取响应,再用jq -e严格断言:
initialize:id == 1 and .result != null,MCP 握手成功;tools/call screenshot:error == null and .result != null,白名单内的工具正常执行;tools/call type_text:返回isError == true,且content[0].text以Permission denied:开头——验证deny.tools显式拒绝;tools/call click(x=1921 越界):同样返回Permission denied:——验证allow.rules中的数值范围约束(x超出0..1920)被强制执行。
这四步恰好覆盖了 YAML 策略的放行、显式拒绝与参数约束三种决策路径。
五、policy-rego.nix:内嵌 Rego 策略引擎的 VM 校验
policy-rego.nix 与 YAML 测试结构几乎一致,区别在于把策略文件替换为一个Rego 策略目录,验证驱动内嵌的 Rego(基于regoruscrate,见 policy.rs)评估能力。
5.1 目录形式的策略
测试在/tmp/policy下写入两个.rego文件,同一package cua.policy:
# base.rego —— 默认拒绝 package cua.policy default allow = false# tools.rego —— 放行与条件放行 package cua.policy allow if input.tool == "screenshot" allow if { input.tool == "click" input.arguments.x >= 0 input.arguments.x <= 1920 input.arguments.y >= 0 input.arguments.y <= 1080 }CUA_DRIVER_POLICY_FILE指向目录/tmp/policy,而不是单个文件。从源码看,PolicyEngine::load 会判断路径是目录还是文件:目录只收集.rego文件(policy_files按文件名排序),单文件则依据扩展名(.yaml/.yml/.rego)选择引擎。
5.2 Rego 求值的输入结构
每次工具调用时,引擎收到如下input:
{ "server": "cua-driver", "tool": "click", "arguments": { "x": 2000, "y": 100 } }策略只须实现data.cua.policy.allow规则并返回布尔值;evaluate对allow求值得到true即放行,false或Undefined即拒绝(错误信息为tool '...' is not allowed by the Rego policy)。测试断言x=2000(超出 1920 上限)的click与type_text都被拒绝,而screenshot被放行。
5.3 两个 VM 测试的共同模式
- 都通过
pkgs.testers.runNixOSTest声明nodes.machine,注入cuaDriver与jq两个 system packages; - 都用
builtins.toJSON把 bash 测试命令安全嵌入machine.succeed(...); - 都在
EXITtrap 中清理 daemon 与 coproc 进程,保证 VM 退出时不留孤儿进程。
六、源码级佐证:策略引擎如何支撑这些检查
NixOS VM 测试所断言的“拒绝”行为,其底层实现在 cua-driver-core/src/policy.rs:
- 环境变量入口:
CUA_DRIVER_POLICY_FILE(POLICY_FILE_ENV,第 11 行)与管理员层级的CUA_DRIVER_MANAGED_POLICY_FILE;策略一旦配置,路径缺失属于配置错误而非隐式关闭(missing_configured_policy_prevents_daemon_startup测试断言 daemon 直接拒绝启动,见 permission_policy_startup_test.rs); - 默认拒绝:YAML 策略对未列出的工具返回
Deny(单元测试yaml_is_deny_by_default),显式deny优先于allow(explicit_deny_overrides_allow); - 约束类型:规则约束支持
min/max(数值)、max_length/pattern(字符串)与allowed(枚举值),见RawConstraint定义(第 542-550 行)与Constraint::matches的求值逻辑(第 634-677 行); - 工具名规范化:
type_text_chars会被规范化映射为type_text后再评估(canonical_tool_name,第 133-138 行),因此 VM 测试中deny: tools: [type_text]同样拦截底层字符级输入工具; - 双层策略交集:
authorize_tool_call(第 298-311 行)把 managed 层与 user 层求交集,用户策略只能收窄、不能放宽管理员上限(单元测试managed_and_user_layers_are_intersected); - 进程内不可变快照:策略经 SHA-256 内容指纹(
policy_sha256)在加载时一次性固化,OnceLock保证 stdio 与 HTTP 请求共享同一份不可变策略快照(第 146-148、226-231 行)。
这些机制正是两个 NixOS VM 检查能稳定断言“Permission denied”的前提。
七、为什么 Nix 中没有“桌面行为目录”
原文档特意强调:本目录刻意不包含桌面行为目录(desktop behavior catalog)。Nix 构建驱动、单元检查、会话依赖与可选的 compositor 包;规范的 GUI 场景与断言位于类型化 Rust 测试框架中。父级 README(nix/cua-driver/README.md)进一步说明原因:旧的 NixOS Python 客户端、GIF 场景、真实应用冒烟矩阵与 compositor 矩阵被移除,是因为它们维护了第二套断言不同的场景目录;权威 Rust 矩阵才是覆盖范围的事实源,退役条目只有在当前类型化用例证明了同一契约时才被视为等价。
因此,新增用户行为覆盖的路径是先进入 Rust 测试框架(例如 cua-driver-testkit/src/raw.rs 的RawDriver这类类型化驱动测试工具),Nix check 可以负责提供会话与包环境,但必须调用共享的 Rust 目录,而不是再定义第二套行为断言。这一点保证了行为契约单一来源、避免断言漂移。
八、如何在本地运行这些检查
在仓库根目录(flake.nix 所在位置)可直接运行:
# 运行全部 flake checks(含上述三个检查) nix flake check # 单独运行某个检查 nix build .#checks.x86_64-linux.cua-driver-linux-rust-unit nix build .#checks.x86_64-linux.cua-driver-policy-yaml nix build .#checks.x86_64-linux.cua-driver-policy-rego需要说明的是,policy-*.nix两个检查依赖 NixOS VM(runNixOSTest),需要宿主具备运行 VM 的能力;rust-unit.nix则只要求在 Nix 可复现环境中编译并运行无头 Rust 测试。若要在真实桌面会话下运行权威 Wayland 矩阵,父级 README 提供了显式入口:
nix develop .#cua-driver-wayland-e2e -c \ scripts/ci/linux/run-rust-e2e-wayland.sh该包装器会启动一个禁用 Xwayland 的纯 Wayland Sway 会话,然后调用与 X11 共用的同一套规范 Rust runner,结果遵循统一 JSONL 模式,并把 MP4 轨迹保留在artifacts/cua-driver/linux/下。
九、小结
nix/cua-driver/tests/的布局遵循一条清晰的设计原则:Nix 负责“可复现地构建与验证策略引擎”,Rust 类型化测试框架负责“定义全部桌面行为契约”。rust-unit.nix守住无桌面会话下的源码与测试链完整性,policy-yaml.nix与policy-rego.nix则把 YAML 与内嵌 Rego 权限策略放进 NixOS VM 中做端到端 stdio MCP 校验。理解这一布局,有助于你在为 cua-driver 贡献测试时把“会话与包环境”和“行为断言”放在正确的位置:新行为先进 Rust 目录,Nix 只负责提供环境。
【免费下载链接】cuaScale computer-use 2.0 with open-source drivers, cross-OS fleets, and benchmarks for training, evaluation, and data generation.项目地址: https://gitcode.com/GitHub_Trending/cua/cua
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考