| R-SKILLS(1.0)落地并合并 main | ✅ 已合并(789007e) | 本轮的迁移 013 依赖 012;skills_agent_set 依赖 skills 表;hash 一致性判据依赖 skill_files。首批 skills 所有者尚未上传(2026-09-03 核对生产 skills_list 为空):不阻塞代码,上传时按「运维」段的顺序来 |
| 画板 1f/1g 加第四组「沙箱执行组」 | ⚠️ 开工时未闭合,所有者裁定不阻塞(云端 Claude Design 项目与本地 design/ 的 Tools 面板示例数据都只有三组;2026-09-03 所有者裁定「正常展示出来就好了」) | 前端按本卡建议值接上(#8b5cf6、组名「沙箱执行组」、示例工具 skill_run)并按此展示;所有者画完后按画板核对这三处并拉稿合并;已记 BACKLOG |
| spike:Encore 的 bun 运行时里 fetch 走 unix socket | ✅ 通过(留证在「本轮实测」) | 两个 --network none 容器经命名卷 unix socket:bun 1.4.0 fetch({unix}) 与 node:http socketPath 都完成 POST /run 往返;Encore 的 JS 运行时不改写全局 fetch(grep encore.dev 无 globalThis.fetch =) |
无新凭据。新依赖:runner 镜像的 Python 基座与 requirements.txt(hash 锁定);api / web 侧零新增 npm 依赖。
| # | 项 | 裁定 | 落点 |
|---|---|---|---|
| 1 | 做不做 | 做,作为 round-skills 的 2.0 迭代 | 本卡 |
| 2 | 规则 9 措辞 | 按默认:「一次性沙箱容器」→「独立沙箱容器(可常驻)+ 每次一次性的进程与工作目录」;残余风险认下 | docs/security.md §1 第 1 层(本文档轮已改) |
| 3 | skill_run 归组 | 按默认:第四组「沙箱执行组」,先改画板 1f/1g | 前置第 2 项;catalog.ts / ToolsPanel.tsx |
| 4 | 隔离强度 | 按默认:network_mode: none + unix socket;spike 不通停下重估 | 前置第 3 项;deploy/docker-compose.yml |
| 5 | 首批 skills | 所有者在 1.0 里经 MCP 上传;默认只展示、不注入,逐 skill 显式打开 | 迁移 013 的 agent_enabled DEFAULT FALSE + skills_agent_set |
| 6 | 改 SKILL.md 要不要发版 | 要发版:agent 可用集合在代码里(runner/skills/),库里只能在集合之内开关;库内副本必须与代码副本 hash 一致才注入 | skills.generated.ts + runner/manifest.json + 一致性判据 |
| 7 | 130 预发 | 非必经;隔离形态在生产冒烟验收,过了才开 skill_run | docs/deploy-environments.md 冒烟清单 +4;打开顺序在「交付物 · 运维」 |
文档(规则 9:本文档轮已完成,开工时若有偏离先改文档)
执行容器 runner/(仓库根,Encore app root 之外,规则 6)
后端(apps/api)
前端(规则 7:零样式改动;三处都是投影 / 绑定逻辑,任务卡在此写明理由 = 接后端的新字段)
测试
| # | 检查 | 命令 / 期望 |
|---|---|---|
| 1 | 编译与全量测试 | dev.ps1 check 通过;dev.ps1 test 全绿(1.0 收口时的文件数 / 用例数 +N,回填) |
| 2 | spike 留证 | Encore bun 运行时里经 unix socket 完成一次 POST /run 往返,记录在「本轮实测」;不通即 BLOCKED |
| 3 | 清单同源 | dev.ps1 skills-gen 重跑后 git diff 为空;篡改 runner/skills 任一字节后漂移测试红;network 字段透传两份清单、缺省 none,手工把某 skill 改成 egress 后它不进可用集合且 runner 对它的 /run 拒绝(R-WEBFETCH C6) |
| 4 | 四个条件真值表 | skills-catalog.test.ts:代码有 / 库无 → 不可用;库有未开 → 不可用;开了但 hash 不等 → 不可用且日志含 drift;四条全真 → 可用 |
| 5 | 入参闭集 | skill_run 的 schema 只有 skill / script / input 三个 string;/agent/tools 响应里 grep 不到 code / path / argv / interpreter |
| 6 | 守卫五条 | guard.test.ts 逐条:未知工具 / 未开放 skill / 非清单脚本 / 非法 input / 超会话次数 → {block:true},reason 不含内部路径;守卫 handler 抛异常 → 仍是拦截 |
| 7 | 轨迹形状 | faux provider 驱动真实 agent loop:被拦截的调用事件序列为 tool_execution_start → tool_call(handlers[0].returned.block=true) → tool_execution_end(isError),无 tool_result;放行的调用 tool_call.handlers[0].returned 为空 |
| 8 | 注入轨迹 | 每轮 before_agent_start 的 handlers 含 xray-skills 与 skills 列表;systemPromptDelta > 0;可用集合为空时不注入且 handlers 记 returned: undefined |
| 9 | 前端投影 | trace-view.test.ts:带 block 的 tool_call 行 hasBadge && hasNote;详情卡 extension 取自 handlers;无 handlers 的事件与今天输出逐字段相同(回归) |
| 10 | 画板对照 | Timeline 拦截行的徽标 / 注记与画板 1a 第 1043 行一致;详情卡与 1b 的 context-injector 卡同版式;Tools 面板第四组与改后的 1f/1g 一致;git diff 不含样式属性改动 |
| 11 | 限额与超时 | reserveSkillRun 原子(并发 20 次只放行上限数);total_timeout_ms CHECK 上下界;超时后 runner 进程组不残留(ps 无子进程) |
| 12 | 输出有界 | 脚本打印 10 MB → runner 截 256 KiB → 工具结果正文 ≤ 8000 + 截断标注;事件 ≤ 8 KB |
| 13 | MCP 四工具 | 真实 2026-07-28 协议路径:tools/list 46;skills_agent_status 对「库里没有 / 未开 / drift / 可用」四种状态各回对应值 |
| 14 | 镜像 | dev.ps1 build 产出三镜像;runner 镜像里 /opt/venv/bin/python -I -c "import sys; print(sys.flags.isolated)" 输出 1;pip 不可用或 --require-hashes 锁定 |
| 15 | 生产冒烟 4 条(裁定 7,双闸关闭状态下跑) | ① skill-runner healthy;② 容器内 socket.create_connection(('1.1.1.1',53),2) 失败、ip route 为空;③ 容器内 touch /opt/skills/x 失败;④ 经 socket 对 /run 发不在清单的脚本名 → 拒绝。四条过了才按打开顺序开启 |
| 16 | 端到端(生产) | 目标段那三句「可证伪」逐条成立;tool_config_set skill_run false 后下一轮工具消失;skills_agent_set text-tools false 后该 skill 不再出现在 before_agent_start 的 skills 里 |
默认继承两条:不改前端页面样式(规则 7);不加设计稿没有的功能(规则 8)。本轮另加:
打开(每一步都可单独回退):发版(runner 镜像 + api + compose)→ 生产冒烟 4 条 → tool_config_set skill_load true → skills_agent_set <name> true(逐个)→ 服务器 .env 加 XRAY_UNLOCK_DANGEROUS_TOOLS=1 并 docker compose up -d api 重建 → tool_config_set skill_run true。
展示副本要与代码副本逐文件一致才会被注入(skills_agent_status 的 consistency: ok),所以打开前先对齐库里的副本(2026-09-03 核对生产 skills_list):
止损:tool_config_set skill_run false 当场停用运行(不发版);skills_agent_set <name> false 单个下线;docker compose stop skill-runner 后工具调用以固定文案失败、站点其余照常。 回退成本:一条纯追加迁移 + 两个工具 + 两个扩展 + 一个容器;.env 去掉 XRAY_UNLOCK_DANGEROUS_TOOLS 并重建 api 即回到「注册表里没有 dangerous 实现」的状态。
同一验收项针对性整改后连续 2 次验证仍不过 → 写 rounds/round-skills/BLOCKED-2.md,停下呼人。禁止放宽验收标准自我通过。 spike(验收 2)不过属前置未满足,同样写 BLOCKED 回所有者重裁裁定 4。
用一份测试 skill(sleep.py / flood.py / fork.py / net.py / probe.py / mem.py)以 bind mount 覆盖 /opt/skills 与 /opt/manifest.json,TCP 开发模式起容器 (--read-only --tmpfs /run/work:noexec,… --cap-drop ALL --pids-limit 64 --memory 384m --cpus 1.0),逐项打:
| 项 | 结果 |
|---|---|
| wordfreq.py / json_pretty.py | exit 0、stdout 是 JSON;坏 JSON 输入 exit 2 且 stdout 是 {"error":"invalid_json"};中文与 emoji 往返无损 |
| 非清单 skill / 脚本(rm.py)/ 哈希不符 / ../runner.py / input 不是对象 | 404 unknown_skill / 404 unknown_script / 409 hash_mismatch / 400 bad_request / 400 bad_request |
| 只读 / env / 隔离模式 | /opt/skills/x 写入 OSError;cwd 是 /run/work/<uuid> 可写;env 只有 HOME / LANG / PATH;sys.flags.isolated == 1、sys.prefix == /opt/venv |
| 超时 3 s 的 sleep 60 | 2.99 s 回 timedOut: true, exitCode: null,stdout 只有超时前的 start |
| 10 MB stdout + 300 KB stderr | 各截到 262144 字节,stdoutBytes: 10485760 如实计数,内存无事 |
| fork 炸弹 | fork stopped at 11 BlockingIOError(NPROC 16 减去 runner 自己的线程)。踩坑:第一版用 BufferedReader.read(65536),它攒够 n 字节或 EOF 才返回,孙进程握着管道时最后一行输出永远读不到、且 11 个睡 30 s 的孤儿留在容器里 —— 改成 read1 + 无论超时与否都 killpg + 收尾 waitpid(-1, WNOHANG) 收僵尸;改后测试结束时容器里只剩 runner 自己(验收 ⑪「进程组不残留」) |
| 400 MB bytearray | rlimit AS 256 MB → MemoryError exit 1,traceback 只在 stderr(api 不转发) |
| 3 个并发 sleep(超时 4 s) | 两个跑到超时被 kill,第三个 503 queue_timeout(排队计入总时长) |
| 镜像 | pip 已从 venv 删除(No module named pip);python -I 隔离位为 1;--network none 起容器 socket.create_connection(('1.1.1.1',53)) 抛 OSError、/proc/net/route 无路由、touch /opt/skills/x 失败 |
① ✅ ② ✅ ③ ✅(skills-gen --check 零漂移;skills-manifest.test.ts 逐文件核;network 缺省 none、egress 不进集合 + runner 拒)④ ✅ ⑤ ✅ ⑥ ✅ ⑦ ✅ ⑧ ✅ ⑨ ✅ ⑩ 部分:1a 拦截行徽标 / 注记、1b 详情卡、1c 链式步骤按画板;Tools 面板第四组画板未画,按建议值接;git diff 无样式属性改动(三处都是投影 / 绑定 / 一条 Record 项) ⑪ ✅(并发 20 只放行 5;CHECK 5000–120000 在 agent-skills.test.ts;进程组不残留在自检)⑫ ✅ ⑬ ✅ ⑭ 部分:本机 docker build runner/ 通过、-I 隔离位 1、pip 不可用;dev.ps1 build 三镜像要等审查收口后跑(所有者纠正过:审查没过不打镜像) ⑮ ⑯ 生产冒烟与端到端:发版之后跑(冒烟清单已加第 19 / 20 条)