OmX Rust Runtime Thin-Adapter 发布门禁解析:从 G1–G5 验证矩阵到兼容视图的权威性设计
【免费下载链接】oh-my-codexOmX - Oh My codeX: Your codex is not alone. Add hooks, agent teams, HUDs, and so much more.项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-codex
导读
本文围绕 OmX(oh-my-codex)项目中决定 Rust 运行时核心(Runtime Core)与 TS 薄适配器(Thin Adapter)切换能否放行的硬性发布门禁文档展开,系统讲解"Rust 引擎作为唯一语义真相所有者、JS/HUD/CLI/tmux 作为只读投递适配器"这一架构在验证、契约、实现与测试四个层面的落地方式。读完本文,你将掌握 OmX 的 G1–G5 门禁矩阵、兼容性工件(Compatibility Artifacts)的优先级规则、Rust 引擎持久化与兼容视图的写入机制,以及 TS 侧 RuntimeBridge 如何以只读方式消费 Rust 权威状态。
为什么需要一份"发布门禁"文档
OmX 的核心团队运行时(team/runtime)正在经历一次大规模架构切换:把权威状态(authoritative state)的语义所有权从 JavaScript 迁移到 Rust core。这个过程中,老一代的 TS 读取器(omx team status、omx doctor --team、HUD、notify/watcher)并不会立刻被删除,而是继续读取由 Rust 引擎输出的兼容视图文件。
这就带来一个典型的迁移风险:如果 Rust 侧已经接管了语义真相,但某些读取器仍在按旧的 JS 默认值或旧的优先级逻辑工作,就会出现"双写发散""语义泄漏进旧读取器""优先级漂移"等问题。为避免这类回归悄悄溜进发布版本,仓库在 docs/qa/rust-runtime-thin-adapter-gate.md 中定义了一份硬性发布门禁(hard gate):任何缺失或失败的场景都会直接导致 CI/发布验证失败。
这份门禁文档本身不是"建议",而是与 CI 测试绑定的强制契约。对应的门禁测试 rust-runtime-thin-adapter-gate.test.ts 会直接读取本文档与契约文档,校验关键段落和 G1–G5 标识是否齐全。
验证矩阵门禁:五道硬性关卡 G1–G5
门禁文档用一张验证矩阵表把"必须被覆盖的场景"映射到"必须存在的测试证据":
| ID | 场景 | 所需证据(测试/文档路径) |
|---|---|---|
| G1 | omx team status读取 manifest 授权的兼容视图 | src/compat/tests/rust-runtime-compat.test.ts |
| G2 | Doctor 保持 manifest 优先的 tmux/session 优先级 | src/compat/tests/rust-runtime-compat.test.ts |
| G3 | HUD 保持 session 作用域状态优先于根级回退 | src/compat/tests/rust-runtime-compat.test.ts |
| G4 | 薄适配器契约文档与读取器兼容车道保持一致 | docs/contracts/rust-runtime-thin-adapter-contract.md + rust-runtime-thin-adapter-gate.test.ts |
| G5 | Watcher send-keys 对等性由 Step 3 配套测试套件覆盖 | notify-hook-team-dispatch.test.ts、notify-hook-team-leader-nudge.test.ts、tmux-detector.test.ts |
这五道关卡分别对应三种旧读取器(团队状态、doctor 诊断、HUD)、一份架构契约文档和 watcher 投递通道。门禁测试会对每个 ID(G1–G5)做正则匹配,并抽查关键语义字符串,例如"Semantic leakage survives into legacy readers""Watcher send-keys parity breaks"等,确保门禁文档没有退化成空壳。
Pre-mortem 场景映射:从"最可能崩在哪"反推门槛
门禁文档还给出了一张"事前验尸(pre-mortem)"映射表,把团队最担心的四种失败模式与上面的门禁一一对应:
| Pre-mortem 场景 | 对应门禁 |
|---|---|
| 语义泄漏存活在旧读取器中 | G1、G2、G3、G4 |
| 读取器优先级在 config/manifest 或 session/root 作用域间漂移 | G1、G2、G3 |
| Watcher send-keys 对等性被破坏 | G5 |
| Mux 契约仍是 tmux 形状而非 Rust 规范形状 | G4 |
这种"先设想失败,再反推验证项"的编排方式,保证了门禁不是按"方便测试"来设计,而是按"迁移最容易踩的坑"来设计。其中"语义泄漏"与"优先级漂移"是同一类风险的两个侧面:旧读取器必须继续工作,但又绝不能把旧的默认值当成真相。
契约核心:谁是语义真相的唯一所有者
门禁 G4 所依赖的契约文档 rust-runtime-thin-adapter-contract.md 首先划定了canonical ownership(权威所有权)边界:Rust core 是以下语义的唯一所有者:
- authority(权威租约)
- lifecycle/session state(生命周期与会话状态)
- dispatch/backlog(派发与积压)
- mailbox delivery state(信箱投递状态)
- replay/recovery(重放与恢复)
- readiness/diagnostics(就绪性与诊断)
- canonical mux operations(规范 mux 操作)
相应地,JS、HUD、CLI 和 tmux 只是薄的投递/观察适配器:它们可以读取兼容性工件,但绝不能自行定义或变更语义真相。
契约还定义了五条薄适配器规则:
- 兼容读取器必须忽略未知字段,并保留现有的 JSON 信封结构;
- 遗留 tmux 键入(typing)仅用于投递,不建立语义真相;
- 当 Rust 作者兼容文件与遗留 JS 默认值冲突时,Rust 作者文件胜出;
- 只有在桥接(bridge)被禁用或不可用的降级通道上,JS 文件写入才允许作为回退;当 Rust bridge 成功时它们不是权威的;
- 未知投递失败应作为适配器失败暴露,而不是作为语义所有者变更。
契约同时给出了消费者矩阵:Team CLI 负责忠实渲染 Rust 兼容工件;Doctor CLI 先报告 Rust 工件的就绪性,再叠加适配器健康检查;HUD 保持只读且感知作用域;Notify/watchers 只负责投递事件,永不成为运行的语义所有者。
兼容性工件与优先级规则
契约文档用一张表明确了三类遗留读取器各自读取的兼容文件与优先级保证:
| 读取器 | 兼容文件 | 兼容保证 |
|---|---|---|
omx team status | .omx/state/team/<team>/config.json、manifest.v2.json、tasks/*.json、approvals/*.json、workers/* | config 与 manifest 同时存在时,manifest 授权的团队配置为权威 |
omx doctor --team | 团队目录下的config.json、manifest.v2.json、workers/*/status.json、workers/*/heartbeat.json、.omx/state/hud-state.json | config 与 manifest 同时存在时,manifest 授权的 tmux/session 身份为权威 |
| HUD readers | .omx/state/session.json、.omx/state/sessions/<session>/team-state.json、.omx/state/team-state.json、.omx/state/ralph-state.json | 会话激活时session 作用域文件为权威,根文件仅是兼容回退 |
这三条优先级规则正是 G1/G2/G3 的验证对象。在 rust-runtime-compat.test.ts 中可以看到它们的端到端验证方式:
- G1(team status):测试先在临时团队状态根目录初始化团队,然后故意让
config.json与manifest.v2.json冲突(config 声明workspace_mode: single、旧 tmux 会话名;manifest 声明worktree、新会话名),再运行omx team status <team> --json并断言输出中的workspace_mode是 manifest 的worktree; - G2(doctor):测试让 config 与 manifest 的
tmux_session冲突,并把一个假的tmux二进制注入 PATH(只回显 manifest 中的会话名),然后运行omx doctor --team,断言输出包含team diagnostics: no issues与All team checks passed.,且不出现resume_blocker; - G3(HUD):测试同时写入根级
team-state.json(active: false、team_name: legacy-root、agent_count: 1)与 session 级sessions/<id>/team-state.json(active: true、team_name: rust-session、agent_count: 3),再调用 HUD 的readTeamState,断言读到的是 session 级数据。
优先级解析在源码中的体现是 src/mcp/state-paths.ts 的resolveStateScope与getStateFilePath:显式 session id > 当前 session id > 根级回退;HUD 侧 src/hud/state.ts 的readAuthoritativeModeState会先解析当前 session id,再拼接<mode>-state.json的路径。
Rust 引擎如何写出这些兼容视图
契约文档明确给出了文件级证据:RuntimeEngine(位于 crates/omx-runtime-core/src/engine.rs)通过persist()与write_compatibility_view()两个方法写出以下文件:
| 文件 | 写入方法 | 内容 |
|---|---|---|
snapshot.json | persist() | 完整RuntimeSnapshot:schema_version、authority、backlog、replay、readiness |
events.json | persist() | 追加式事件日志,RuntimeEvent数组(#[serde(tag = "event")]格式) |
authority.json | write_compatibility_view() | 供 TS 读取器使用的AuthoritySnapshot分区 |
backlog.json | write_compatibility_view() | BacklogSnapshot计数(pending/notified/delivered/failed) |
readiness.json | write_compatibility_view() | ReadinessSnapshot(ready、reasons) |
replay.json | write_compatibility_view() | ReplaySnapshot状态 |
dispatch.json | write_compatibility_view() | 完整DispatchLog(DispatchRecord数组),供团队状态读取器使用 |
mailbox.json | write_compatibility_view() | 完整MailboxLog(MailboxRecord数组),供团队/消息读取器使用 |
从源码看,两个方法的职责划分非常清晰(engine.rs):
persist()(约第 291 行)会先创建engine.lock并加排他锁,随后写入snapshot.json、events.json、mailbox.json、dispatch.json,并额外落盘dispatch-seen.json(派发去重账本,schema_version=2、ledger_epoch=1),保证崩溃后已接受的 request_id 永不重用;write_compatibility_view()(约第 322 行)则基于snapshot()的结果把各分区拆成独立小文件,方便 TS 读取器只读自己关心的部分。
所有文件都写入配置的state_dir(原子替换 + 目录 fsync,见persist_dispatch_seen_ledger与sync_directory)。契约明确要求:TS 读取器必须把这些文件视为只读,Rust 引擎是唯一写入者。
CLI 二进制 crates/omx-runtime/src/main.rs 提供了与引擎配套的子命令:schema [--json](契约摘要)、snapshot [--json] [--state-dir=DIR]、exec <json> [--state-dir=DIR] [--compact]、init <state-dir>、mux-contract以及fs-rename-no-replace、process-identity。其中exec每次执行都会先加runtime-mutation.lock排他锁,然后load→process→ (可选compact)→persist→write_compatibility_view,即一次命令完成"权威持久化 + 兼容视图刷新"两件事。
TS 侧薄适配器:RuntimeBridge 的只读消费
门禁所依赖的另一半实现是 src/runtime/bridge.ts 中的RuntimeBridge,它是 TS 侧对omx-runtime二进制的薄封装。它的三条设计原则在文件头注释中写明:
- 所有语义状态变更都经由
execCommand()路由到 Rust 二进制; - 所有状态查询都读取 Rust 作者兼容 JSON 文件;
- 设置
OMX_RUNTIME_BRIDGE=0可禁用桥接(回退到 TS 直写)。
桥接的读取接口高度模块化:readAuthority()、readReadiness()、readBacklog()、readDispatchRecords()、readMailboxRecords()分别对应authority.json、readiness.json、backlog.json、dispatch.json、mailbox.json。其中readCompatFile<T>()是统一入口,它对"文件正在被原子替换(读到空内容)""临时文件尚未落定"等跨边界场景做了容错——返回null让上层本 tick 回退到 JS 推断状态,而不是抛异常打断整个查询路径。
对于要求更严格的读取场景(如 dispatch 循环),readDispatchRecordsStrict()会"失败关闭(fail closed)":状态目录不可用、文件缺失、形状非法、记录不满足DispatchRecord严格校验(request_id/target/status/时间戳/metadata字段类型逐一检查)时直接抛出RuntimeBridgeError。execCommand()对 Rust 返回非 JSON 输出同样抛出带上下文的RuntimeBridgeError,让 dispatch 调用方可以用instanceof精确处理解析失败,而不是让SyntaxError冒泡到无关层次。
桥接的启动还包含一次契约自检:validateSchemaOnce()会调用omx-runtime schema --json,校验预期命令集合(acquire-authority、renew-authority、queue-dispatch、mark-notified、mark-delivered、mark-failed、remove-dispatch-records、request-replay、capture-snapshot)是否齐全,缺命令即判定"TS 桥接类型与 Rust 二进制不同步"。二进制定位顺序见resolveRuntimeBinaryPath():OMX_RUNTIME_BINARY环境变量覆盖 → 已验证的 native 缓存(带.sha256伴生校验文件)→ workspacetarget/debug/omx-runtime→target/release/omx-runtime→ 回退到 PATH 上的omx-runtime。
在 rust-runtime-compat.test.ts 的第四个测试(约第 226 行)中,可以看到"桥接兼容视图胜过陈旧遗留文件"的端到端验证:测试先在遗留dispatch/requests.json与mailbox/worker-2.json中预置仅存在于旧通道的legacy-only记录,再通过enqueueDispatchRequest/sendDirectMessage走真实桥接写入,最后断言listDispatchRequests/listMailboxMessages读到的是桥接记录,旧遗留记录不再出现——这正是 thin-adapter 规则 3(Rust 文件胜出)的直接证据。
Watcher send-keys 对等性(G5)
门禁 G5 关注的是 notify/watcher 通道的投递对等性:当 Rust bridge 接管权威状态后,watcher 仍然需要通过 tmuxsend-keys向目标 pane 投递键击,这条投递路径不能因迁移而改变形状。证据落在三个测试套件:
- notify-hook-team-dispatch.test.ts:断言通知 hook 产生的
send-keys -t <pane>目标 pane 正确(如send-keys -t %99),且不会错误命中开发会话或替换 pane; - notify-hook-team-leader-nudge.test.ts:覆盖 leader nudge 场景下的投递目标;
- tmux-detector.test.ts:覆盖 tmux 环境检测。
与 G4 呼应的是:投递层允许"tmux 形状",但规范 mux 操作(canonical mux operations)的所有权在 Rust。契约与 docs/interop-team-mutation-contract.md 都强调,直接 tmux 键入只是操作层面的回退(operational fallback),绝不构成变更契约——broker 必须通过 JSON 信封 + 状态读取来确认变更是否成功。
门禁之外:非门禁的后续 seam audit
门禁文档最后明确指出:当前 thin-adapter 切换仍存在少数已知的接缝缺口(seam gaps),它们被有意地排除在发布门禁之外,记录在 docs/qa/runtime-team-seam-audit-2026-04-01.md(基线提交51579ce,issue #1108 之后的快照)。
这份 audit 记录了四个接缝点,其中两个已解决、两个待跟进:
- Rust runtime ↔ TS team state 双写:已由 issue #1108 解决。
src/team/state/dispatch.ts与src/team/state/mailbox.ts中的 Rust 桥接/兼容文件现在是 dispatch 与 mailbox 的权威面,遗留 TS 文件仅在桥接禁用/不可用时作为降级回退; - 团队元数据解析横跨多个文件(未解决):src/team/api-interop.ts(约第 423–438 行)先查 worker identity 元数据,再查
manifest.v2.json,最后查config.json,工作目录/状态根解析可能依赖回退顺序而非单一权威源; - 运行时所有权契约 vs 切换现实:dispatch/mailbox 所有权已与契约一致,剩余工作是元数据/回退层的简化;
- 兼容读取器仍携带回退优先级逻辑(未解决):rust-runtime-compat.test.ts(约第 47–170 行)与 src/hud/state.ts(约第 107–123 行)有意保留旧优先级以保证迁移安全,但这也让读路径比目标架构更复杂,未来格式漂移可能藏在回退行为里。
audit 给出的后续顺序是:先把团队状态根/工作目录解析收敛到单一规范元数据源,再在写路径单所有者之后削减兼容回退层。门禁文档之所以把这些排除在外,正是因为它们属于"演进方向"而非"切换正确性"——切换本身(权威性、优先级、投递对等)已由 G1–G5 牢牢锁住。
如何在本仓库中验证门禁
门禁测试与契约测试均使用 Node 内置node:test编写,可从仓库根目录直接运行:
# 校验门禁文档与契约文档的关键语义(G4) node --test src/verification/__tests__/rust-runtime-thin-adapter-gate.test.ts # 端到端验证 G1/G2/G3 及桥接兼容视图优先级 node --test src/compat/__tests__/rust-runtime-compat.test.ts # G5 配套套件 node --test src/hooks/__tests__/notify-hook-team-dispatch.test.ts node --test src/hooks/__tests__/notify-hook-team-leader-nudge.test.ts node --test src/notifications/__tests__/tmux-detector.test.tsRust 侧的引擎单元测试(persist/load往返、兼容视图分区文件写出、dispatch 去重账本、mailbox body 回填等)位于 crates/omx-runtime-core/src/engine.rs 的#[cfg(test)] mod tests中:
cargo test -p omx-runtime-core注意rust-runtime-compat.test.ts会真正 spawndist/cli/omx.js,需要先完成 TS 构建;同时它会通过OMX_TEAM_STATE_ROOT、OMX_RUNTIME_BINARY、PATH等环境变量注入隔离的临时状态目录与假tmux/假 runtime 二进制,因此验证时不依赖真实环境。若在部分受限文件系统权限下运行出现EPERM/EACCES,测试会按shouldSkipForSpawnPermissions跳过 spawn 类断言。
小结:门禁文档的工程价值
rust-runtime-thin-adapter-gate.md看似只是一张核对清单,但它实际上定义了一套可执行的架构治理机制:
- G1–G5 验证矩阵把"Rust 权威、TS 只读"的架构原则翻译成了可断言的测试证据;
- Pre-mortem 映射确保门禁覆盖的是真实迁移风险而非随机场景;
- 契约文档划清了权威所有权与五条薄适配器规则,任何一边越界都会被门禁测试或运行时校验抓住;
- 非门禁 audit则把"正确性"与"演进"分开治理,既不放行已知回归,也不阻塞架构优化。
对任何正在做"语言/运行时边界重构 + 老读取器兼容"的团队来说,这套"契约文档 + 门禁矩阵 + 端到端兼容测试 + 非门禁跟进审计"的组合,是一个可以直接借鉴的迁移治理模板。
【免费下载链接】oh-my-codexOmX - Oh My codeX: Your codex is not alone. Add hooks, agent teams, HUDs, and so much more.项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-codex
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考