Merge feat-chv-muse-p9: inference/telemetry restricted channel

- executor.py: P9 _inference_gate inserted before P1 dispatcher
  _run_with_vm; gate call is the first statement, so deny/ask
  fail-closed before any resident resume or cold wake. P9's own
  _run_with_vm (old-body variant) dropped: its body is byte-identical
  to P1 _run_cold (verbatim move), kept once in _run_cold.
- sentinel.py: P9 superset version wins (P5 base + inference entries
  POST /v1/responses, GET /v1/models + comments).
- inference_channel.py / telemetry.py / host.py: P9 as-is.
- Tests: P9 51 + closeup/gaps/optimize/cloud_hypervisor/fix 272 OK.
- mypy on changed files: P9's 5 cross-track errors gone (P5
  capabilities.py now in tree); remaining 4 are 06c3e17 baseline
  legacy (marker_tools.py:142, executor.py:1573/1578), untouched.
This commit is contained in:
host committed 2026-11-07 00:09:26 +00:00
commit 90c42f6e1d
13 files changed
+1455 -15

No files matched your search

+45
View File
@@ -0,0 +1,45 @@
# CHV VM 运营访问边界说明(feat-chv-muse-p9)
> 口径依据:Meta 官方安全文(security.muse.ai 2026-09-08)对 Muse 当前架构的
> 明确表述——当前架构**通过运营政策限制 Meta 人员访问用户数据,但并不阻止
> Meta 在必要时为支持、保障或运营服务而访问数据**;“连 Meta 也看不到”是未来
> Confidential VM 的目标,**截至 2026-11-01 的公开材料中未见 Confidential VM
> 已全面上线**。本文件把同一口径落到本项目:只写文档 + 审计钩子,
> **不冒充技术上不可访问**。
## 1. 谁能访问 VM 内数据
| 访问方 | 能访问什么 | 边界 |
|---|---|---|
| 用户本人(VM 属主) | 全部:工作区文件、记忆、凭证替身、审计记录 | 属主即所有者;可检查、编辑、下载 VM 内所有文件,也可要求忘记指定内容(见 P7) |
| Agent(codex,单元内) | 任务所需的工作区与工具输出 | 受 nspawn 单元(P2)、authd 替身凭证(P3)、Sentinel 出口审批(P3/P5)约束;真凭证与宿主侧数据对单元内不可见 |
| 宿主运维(部署机器的 root / codexsvc) | VM 磁盘镜像、state_dir、日志、审计 | **技术上可以访问**:宿主侧持有磁盘文件与进程,OS 账户即边界。本文件不声称运维看不到——只规定运维**应在什么情况下**访问(见 §2),并要求访问留审计 |
| Meta(对照口径) | 不适用——本项目无 Meta 侧运营方 | 本项目是用户自部署,不存在 Meta 人员访问;写这一行只为对齐口径:若未来托管化,边界按同一句话执行 |
## 2. 运维访问规则(政策,非技术强制)
1. **日常不看**:运维不主动翻看用户工作区文件、对话记录与记忆;只看任务级元数据(任务 id、起止、成败、耗时)与审计事件。
2. **排障可看**:任务失败、VM 异常、安全事件调查时,运维可查看相关 VM 的日志、审计记录与任务输出;查看行为本身记入审计(`chv_audit` + 操作日志)。
3. **凭证永不直接取**:真凭证(Gitea token、网关 key)只经 authd 替身释放;运维排障需要凭证时走替身铸发流程,不直接读 seed 文件;seed 文件 0600,属主 root/codexsvc。
4. **数据不出宿主**:排障取证不出部署机器;必须外发时先去标识化(任务 id 可留,对话原文、文件内容、密钥一律去掉),并记审计。
5. **删除即删除**:用户要求删除任务数据/记忆时,运维删除 VM 盘、state_dir 与相关遥测行,并记审计;备份里的副本按备份保留期滚动清除,不单点恢复。
## 3. 审计(可查的证据)
- 任务生命周期审计:`chv_audit`(执行器 `_emit`,失败落盘 `chv-audit-spill.jsonl`)。
- 出口审批审计:Sentinel 决策记录(`sentinel_egress_allow/deny/ask`,只含目标描述与原因,不含值)。
- 训练开关审计:`training-opt-audit.jsonl`(开关变更时间、开/关、操作方)。
- 遥测:`telemetry.jsonl`(只含任务级元数据字段,见 `docs/chv-muse-p9-gap.md` §2.1)。
审计记录里绝不含凭证值、对话原文与文件内容;审计失败不中断任务(worker 层 best-effort 语义,见执行器 `_emit` 注释)。
## 4. 广告隔离(口径,不用写代码)
- 本项目不把用户对话或 VM 数据分享给任何广告系统;遥测字段白名单里没有可用于广告画像的字段。
- Agent 浏览互联网时表现为用户活动,可能间接影响用户在别处看到的广告(与 Muse 官方口径一致)——这是浏览行为本身的副作用,不是本项目分享数据。
## 5. 明确未做的事
- **Confidential VM**:未做。Meta 自己也未交付(路线图),本项目不自研密码学隔离 VM;当前边界是 OS 账户 + 文件权限 + 审计,不是密码学不可访问。任何“连运维也看不到”的说法在本架构下都是错的,不要这样对外写。
- **端到端加密记忆**:未做。记忆文件在 VM 盘上明文存放(权限 0600),依赖磁盘与宿主访问控制。
- **操作审批 UI**:运维访问的审批流程目前是人工约定(本文件),没有强制审批卡;强制审批是后续工作,不在本轨冒充已实现。
+157
View File
@@ -0,0 +1,157 @@
# 对齐 Muse P9 差距表:推理与遥测受限通道
> 先写表再动手。依据:`~/workspace/research_notes/meta-muse-ai-architecture-20261101-0755/report.md`
> (Meta 官方 security.muse.ai 与设计文 2026-09-08 为准;第三方拆解只作参考,不得把逆向细节写成官方架构)。
> 基线:`06c3e17`。分支:`feat-chv-muse-p9`。本地做,不推、不合、不部署。
## 1. 现状 vs Muse 官方要求
| # | Muse 官方要求(security.muse.ai / 设计文) | 现状(基线 06c3e17) | 本轨做法 |
|---|---|---|---|
| 1 | 推理与遥测代理是通向外部基础设施的**受限通道** | 模型调用无收敛点:`host.py` 起 `codex app-server`,provider=`sub2api` 时 `-c` 覆盖 base_url(取 `SUB2API_BASE_URL`);provider 为空时 codex 用自身 config.toml 默认网关(xcmapi 路径),执行器侧没有任何出口校验 | 新模块 `cloud/chv/inference_channel.py`:单一出口点 + 执行器 `_run_with_vm` 起 VM 前预审(Sentinel 评估);`host.py` 的 `_server_command` 做结构校验(https、无 userinfo、主机在 allowlist 内)当第二道防线 |
| 2 | VM 发送**有限数据**用于推理与遥测(安全文**未给字段级清单**) | 无任何遥测:任务元数据只进宿主日志与审计 spill,无字段清单、无采样、无发送路径 | 新模块 `cloud/chv/telemetry.py`:字段级白名单(只含任务级元数据,见 §2);采样率可配;落盘 `<state_dir>/telemetry.jsonl`,发送失败只记日志、不影响任务 |
| 3 | 推理轨迹(对话往返、工具调用、子代理交接)在**去除关键个人身份信息后**可用于训练新检查点;用户可**一键退出训练** | 无训练上传、无开关 | 训练退出开关(`training_opt`,见 §2):状态持久化在 `<state_dir>/training-opt.json`,关后轨迹类事件不再产生;开关变更进审计。**默认值开是本项目决定,非 Meta 官方值**(报告注明帮助中心原文未逐字确认默认开启) |
| 4 | 当前 VM 的 Meta 访问边界(官方明确,非营销口径):运营政策限制 Meta 人员访问用户数据,但**并不阻止** Meta 在必要时为支持、保障或运营而访问;“连 Meta 也看不到”是未来 Confidential VM 的目标 | 无边界说明 | `docs/chv-access-boundary.md`:只写文档 + 审计钩子,不冒充技术上不可访问;Confidential VM 明确列为未做 |
| 5 | 广告隔离:不把用户对话或 VM 数据分享给广告系统;但浏览互联网时表现为用户活动、可能间接影响广告 | 无口径 | 只记为口径(见 §4),不用写代码 |
| 6 | Sentinel 是连接器动作与全部网络出口的唯一权限机构 | P3 已做 Sentinel 最小审批、P5 已做纵深(本基线**都没有**;P3 在 `feat-chv-muse-p3` 分支 a90cf78,P5 在 `feat-chv-muse-p5` 分支 25bcd35) | 推理通道走 Sentinel 审批,不重复造审批:`cloud/chv/sentinel.py` 从 **P5 的 25bcd35 拷入**(P5 版已是 P3 的超集,顶层仍只用标准库),再加 P9 的 inference 条目(三张表里加 `inference`:scheme https、端口 443、路径 `POST /v1/responses` + `GET /v1/models`);合入时以本文件为准(它是 P3+P5+P9 的并集) |
## 2. 本项目自定清单(非 Meta 官方值,逐项注明)
### 2.1 遥测字段白名单(`telemetry.py`)
只含任务级元数据,**绝不含**对话原文、文件内容、密钥、个人身份信息:
| 字段 | 含义 | 来源 |
|---|---|---|
| `task_id` | 任务 id | 任务 dict |
| `vm_id` | VM 名 | 执行器 |
| `provider` | 模型 provider(`sub2api` / `xcmapi`) | 通道决策 |
| `model` | 模型名 | 任务 dict |
| `status` | `completed` / `failed` / `interrupted` / `timeout` | 运行结果 |
| `started_at` / `ended_at` | 起止时间戳(秒) | 执行器 |
| `duration_ms` | 耗时毫秒 | 执行器 |
| `tokens_in` / `tokens_out` / `tokens_total` | token 数 | host.py 回传的 usage(未回传时记 `null`,**不编造**) |
| `turns` | 回合数 | 运行结果 |
| `tool_calls` | 工具调用次数 | 运行结果 |
| `sampled` | 是否被采样命中 | 采样器 |
不在白名单里的键一律丢弃;键名含 `key` / `token` / `secret` / `password` / `prompt` /
`content` / `text` / `message` / `email` / `name` 的一律丢弃(防误带)。采样率
`CHV_TELEMETRY_SAMPLE_RATE`:0.0–1.0,默认 `1.0`(全量,任务级元数据量小);按
`task_id` 确定性采样(同一任务每次判定一致,可测试)。
### 2.2 轨迹事件(训练用)
轨迹类事件只含回合级元数据(回合数、工具名、耗时),**不含任何文本**;去标识化
= 只出元数据、不出内容。退出开关关后,轨迹类事件**不再产生**(不是产生后丢弃)。
### 2.3 推理通道上限(自定)
| 项 | 环境变量 | 默认 | 范围 | 说明 |
|---|---|---|---|---|
| 单次请求超时(秒) | `CHV_INFERENCE_TIMEOUT` | `120` | 10–600 | 与 `host.py` 的 `Server.request` 默认超时一致;非法值即 ValueError |
| 重试上限(次) | `CHV_INFERENCE_MAX_RETRIES` | `1` | 0–5 | `Server.request` 的 BrokenPipe 重连次数上限;默认 `1` 即**保持现有行为不变**(现为 `range(2)`);模型回合级重试仍是 worker 的 `max_retries` 语义,本轨不动 |
### 2.4 训练退出开关默认值
默认**开**(轨迹可用于训练)。这是**本项目决定,非 Meta 官方值**:报告注明帮助中心原文未逐字确认
默认开启。开关状态持久化、变更进审计;关后立即生效(后续轨迹不再产生)。
### 2.5 推理出口 allowlist 与 origin 规则
- provider=`sub2api`:origin 取 `SUB2API_BASE_URL`(去尾斜杠);未知 provider 标签与
provider 为空一样走默认网关(与 host.py `_server_command` 一致——标签本身不决定
origin,origin 永远只取 operator 配置的 env)。
- provider 为空或未知标签(xcmapi 路径,codex 用自身 config.toml 默认网关):执行器
**看不到** origin,改取 `CHV_INFERENCE_ORIGIN`(operator 显式配置,如 `https://xgapi.lol`);
未配即 `ask`——审计 + 任务停止(fail-closed),**不静默放行**。部署迁移:现网 xcmapi 部署
须补设 `CHV_INFERENCE_ORIGIN`,否则任务会在预审处停下(这是有意行为,见 §3)。
- scheme 规则(2026-11-07 全量套件复核时修正——初版只认 https 是错的):
- https:公网网关(如 xgapi.lol),端口只接受 443(或缺省);
- http:**只接受本地中继**——字面环回/私网 IP 主机(如 cloud/README.md 的 sub2api 网关
文档形状 `http://172.17.0.1:28084`,Docker 桥接 IP),端口保留;http + 公网主机名/IP、
http + 主机名一律拒绝。主机名一律不算本地中继(DNS 结果不可信)。
- 本地中继分支:字面 IP 无 DNS、无重绑定,跳过 Sentinel 的 DNS/SSRF 步;allowlist 与
单次能力(P3 默认档)照常执行;解析器不被调用(测试用必错解析器钉住)。Sentinel 表里的
inference scheme/端口仍只写 https/443——本地中继不走 `evaluate`,不要“顺手”把 http
加进表(那会放宽公网路径)。
- 路径规则(`inference` 连接器):只放 `POST /v1/responses` 与 `GET /v1/models`;
`/v1/models` 是否必需**待真机核实**(codex 启动时是否拉模型列表),若非必需则收紧为只放
`/v1/responses`。公网路径:解析到私网 IP 即 deny(SSRF,复用 Sentinel);解析失败即 deny。
- 任务级 env 合并:门的 origin 取任务级 `extra_env`(网关配置随任务来,如 worker 的
`_gateway_env`)覆盖执行器进程 env;origin 非法(http 公网、带 userinfo、https 非 443
端口)记 deny,不抛裸 ValueError。报错/审计原因不带 origin 原值(http URL 里可能含
userinfo,见 `resolve_inference_origin`)。
## 3. 改什么(文件清单)
| 文件 | 动作 |
|---|---|
| `cloud/chv/sentinel.py` | **新增**:从 P5 分支 `25bcd35` 拷入(P5 版已是 P3 的超集,顶层仍只用标准库),再加 P9 的 inference 条目(三张表:路径、scheme、端口);合入时以本文件为准(P3+P5+P9 的并集) |
| `cloud/chv/inference_channel.py` | **新增**:受限推理通道(origin 解析、allowlist、上限校验、Sentinel 预审、`InferenceChannelDenied` 之外的 Decision 返回) |
| `cloud/chv/telemetry.py` | **新增**:遥测事件(字段白名单、采样、落盘、永不抛错)+ 训练退出开关(持久化、审计) |
| `cloud/chv/executor.py` | **改动**:`_run_with_vm` 起 VM 前调推理预审(`_inference_gate`);allow 通过、deny/ask 审计 + `ChvFailure` 停止,不起 VM。`execute()`(只读 status,不跑模型回合)不设门 |
| `host.py` | **改动**:`_server_command` 的 sub2api origin 结构校验(https、无 userinfo、主机在 allowlist 内);`Server.request` 默认超时与重连上限改走 `CHV_INFERENCE_TIMEOUT` / `CHV_INFERENCE_MAX_RETRIES`(默认 120 秒 / 1 次,即现有行为) |
| `cloud/tests/test_chv_muse_p9.py` | **新增**:全部覆盖(见 §5) |
| `docs/chv-muse-p9-gap.md` | **新增**:本文件 |
| `docs/chv-access-boundary.md` | **新增**:运营访问边界说明 |
**不碰的**:`cloud/chv/config.py`(通道读 `env` 字典,不进 VM 配置片段;架构测试无“env 必须进片段”的规矩)、
P3 的 authd、P5 的 eBPF 污点(P5 已结论:本 VM 的 bpf() 被 seccomp 挡,不可行;本轨不重复探)、
P6 的确定性检查层(审计复用执行器已有的 `_emit`,不另造审计接口)、worker 的 `max_retries`、
guest 镜像(`host.py` 是标准库单文件,`/app/host.py` 拷入方式不变——本轨对 `host.py` 的改动只用标准库)。
## 4. 诚实边界
- 本轨是**决策层 + 预审**,不是网络层强制:客内 codex 直连的强制仍靠 Tinyproxy 白名单 + 宿主防火墙
(第三棒落地中)。不要把本轨写成“出口已全部经 Sentinel”——P3 的模块 docstring 同款声明。
- provider 为空时执行器看不到 codex config.toml 里的 origin:本轨用 `CHV_INFERENCE_ORIGIN`
显式配置 + 未配即 ask 兜底;这不是“读到了 config”,部署时须补 env(见 §2.5)。
- 遥测目前只落盘 `<state_dir>/telemetry.jsonl`,**没有**向外部遥测端点发送的路径;“发送失败不影响任务”
指落盘失败也不影响任务。向外部发遥测是后续工作,需另批。
- 训练轨迹目前**没有**上传路径:开关管的是“轨迹类事件是否产生”;上传通道本身不存在,开关是为
上传通道预留的门禁,不是已接通的上传。
- token 数只取 host.py 回传的 usage;codex 未回传时记 `null`,不估算、不编造。
- `GET /v1/models` 是否必需未验证(见 §2.5),真机核实前不收紧、也不放宽。
- `sentinel.py` 是从 P5 分支(25bcd35)原样拿来的超集版本:其中 `capability_for` 的非 one-shot
分支在运行时 import `cloud.chv.capabilities`——该模块是 P5 的文件,本轨基线 06c3e17 里没有。
本轨只走 one-shot 默认档,运行时不触及该分支(新测试 51/51 证明);mypy 会报 1 处
`import-not-found`,待 P3/P5/P9 集成轨把 capabilities.py 带进来后消除,不在本轨另造一份。
其余 mypy 报错与基线一致:`executor.py:1396/1401`(`out` 未注解)与 `marker_tools.py:142`
是 P3 已记录的基线问题,本轨未碰。
## 5. 测试清单(`test_chv_muse_p9.py`,全部用标准库与 mock,不联网、不启动真实 VM)
- origin 解析:合法 origin 通过;缺 `SUB2API_BASE_URL` 即错;http 公网主机名/IP 被拒;
http 本地中继(字面环回/私网 IP,如 `http://172.17.0.1:28084`)通过且端口保留;
URL 带 userinfo 被拒;尾斜杠被剥掉;未知 provider 标签走默认网关(不 deny)。
- allowlist:`CHV_INFERENCE_ALLOW_HOSTS` 显式覆盖生效;默认即 origin host。
- 上限校验:超时/重试非法值(非整数、越界)即 ValueError;默认值 120/1。
- 预审决策(假解析器,不联网):allow(公网 IP + 白名单 + 路径内);deny(解析到私网 IP、
origin 非法);ask(主机不在白名单、provider 为空且未配 `CHV_INFERENCE_ORIGIN`);
路径越界(`GET /v1/admin`)即 deny;能力绑定(单次、300 秒);本地中继 allow(不查 DNS,
用必错解析器钉住)与本地中继 ask(allowlist 不含该主机)。
- 旁路:直连(非通道)调模型无入口——执行器 `_run_with_vm` 前必过预审(mock 执行器验调用顺序:
预审 deny 时 `wake_vm` 未被调用);任务级 `extra_env` 的 origin 被合并进预审(worker 网关
路径),非法 origin 转 deny 而不是裸 ValueError。
- 遥测:白名单外键丢弃;`prompt`/`content`/`api_key` 类键丢弃;采样确定性(同一 task_id 多次判定一致;
采样率 0 全丢、1 全留);落盘失败(state_dir 不可写)不抛错、不影响任务;token 未回传记 `null`。
- 训练开关:默认开;关后轨迹事件不再产生、任务级遥测不受影响;开关变更持久化 + 审计有记录;
状态文件损坏时 fail-closed(当作关处理,记日志)。
- host.py:`_server_command` 的 origin 结构校验(http 公网被拒、本地中继通过、allowlist 不匹配
被拒、合法通过);`Server.request` 默认超时与重试上限走 env(mock 验默认值 120/1 与 env 覆盖)。
- 旧测试 harness:5 个文件的 `make_exec` 配了假 origin(`SUB2API_BASE_URL` 与
`CHV_INFERENCE_ORIGIN` 均为 `https://xgapi.lol`)+ 假公网解析器——这是新 fail-closed 门
要求的测试配置,不是放宽门;门的 deny/ask 行为由本轨新测试钉住。
## 6. 真机待验证清单(排队,测试机空闲后;第三棒 → round-11 → P4/P5/P6 → 本轨)
1. `GET /v1/models` 是否必需:真机跑一轮 turn,确认 codex 是否调它;必需则保留,不必需则收紧为只放 `POST /v1/responses`。
2. 预审对真实 xgapi 主机的判定:`https://xgapi.lol` 解析后 IP 是否干净(不在 SSRF 挡截网段内);若解析到被挡网段,记录真实判定、不绕过。
3. `CHV_INFERENCE_ORIGIN` 未配时 xcmapi 任务是否如实停在预审(ask + 审计),配上后是否放行。
4. 本地中继真机:`SUB2API_BASE_URL=http://172.17.0.1:28084` 的任务预审是否放行(本地中继分支)、
真实 turn 是否走通该中继;同时确认 http 公网 origin 在真机仍被拒。
5. 真实 turn 的 usage 是否回传:遥测里的 token 字段是否从 `null` 变成实数。
6. 遥测落盘文件在真实任务后是否存在、内容是否只有白名单字段。
7. 训练开关在真机上的持久化:开关翻转后重启执行器,状态是否还在。
7. 超时/重试上限在真机上的表现:长 turn 是否在 `CHV_INFERENCE_TIMEOUT` 处超时(只记录、不主动造超时)。