boardx / boardx/workspacex

[F962] 试跑接真执行:沙箱化脚本执行 + 真实 .pptx 产物(skill-sandbox-execution 首个切片)

Open
#1,583 4 comments 0 reactions 0 assignees View on GitHub
area:ai-runtime sprint:01-01 status:in-progress
Dominant language
TypeScript
Stars
0
Forks
0
Avg merge
1h 7m
Merged PRs (30d)
969

Description

## 交付契约(user_visible_behavior)

组织成员在 /admin/skill 对一个 pptx 类 skill 版本点「试跑」并给出样例需求后,系统不再只回一段文字:模型写出 pptxgenjs 脚本,脚本在一个 network:none、只读 rootfs、非 root、workdir 挂 tmpfs 且带内存/CPU/pids 上限与 wall-clock 硬超时的容器里真实执行;脚本首次失败时,真实的 exitCode 与 stderr 被回喂给模型重写(最多 3 次);成功后试跑结果里出现一个可下载的 .pptx 引用,下载下来是一个能被 PowerPoint/Keynote 打开的合法 OOXML 文件(zip 完整、[Content_Types].xml 声明 presentation、每张 slideN.xml 可解析、正文非空),页数与内容与请求对应。沙箱不可达 / 超硬超时 / 重试用尽三种失败各自回不同的失败码,最后一种把最后一次的真实 stderr 原样带回,不翻译成「请重试」。⚠ 试跑产物落 ObjectStore 但**不自动**落成项目产物。

## Story

[requirements/03-skill/uc-3-1-上传并校验一个-skill.md](https://github.com/boardx/workspacex/blob/main/phases/phase-01-run-a-project/requirements/03-skill/uc-3-1-上传并校验一个-skill.md) — 章节 `R3`

## 验证(完成的唯一标准:每条命令退出码 0)

- [ ] `node .harness/scripts/lint-arch-deps.mjs apps/api/src`
- [ ] `node apps/api/scripts/lint-permission-paths.mjs`
- [ ] `bash apps/api/scripts/verify-migrations.sh`
- [ ] `pnpm --filter @repo/skill-sandbox run typecheck`
- [ ] `pnpm --filter @repo/api run typecheck`
- [ ] `pnpm --filter @repo/skill-sandbox exec vitest run tests/execute-script-isolation.test.ts tests/produces-real-pptx.test.ts`
- [ ] `pnpm --filter @repo/skill-sandbox exec vitest run tests/container-network-isolation.test.ts`
- [ ] `pnpm exec tsx .harness/scripts/with-test-isolation.ts -- pnpm --filter @repo/api exec vitest run tests/skill/sandbox-retry-and-failure-codes.test.ts tests/skill/sandbox-onion-dependency-direction.test.ts tests/skill/trial-run-async-real-db.test.ts tests/skill/trial-run-store-reads-are-actor-scoped.test.ts tests/skill/trial-run-skill-model-resolution.test.ts tests/skill/org-agent-model-reader-repo-guard.test.ts`

证据落盘:`phases/phase-01-run-a-project/sprints/sprint-01/evidence/F962.verify.log`

## 实现指引(notes)

① ID 以 design-signoff.md 的 covers 为准 = **F962**(origin/main 已合入的机械重编号,理由见该文件 covers 行注释)。⚠ 本分支先后自行编过 F210、F211 两个号,**都作废**:F204 被 #1531 占用、F210 被 #1588 占用,而我每次都照**开工那一刻的快照**选号,快照又一次过期。这是 AGENTS.md「静态痕迹 ≠ 动态事实」连栽两次 —— ID 空间会被并行会话推进,**号只有一个权威来源:main 上的 signoff**,不由 worker 自行分配。已按 coord 裁定对齐 F962,不留并存的第二个号。
② 隔离是**两层**,缺一不可(contract §4):L1 容器 network:none 挡网络,L2 Node 权限模型挡文件越界与子进程。⚠⚠ L2 **挡不住网络** —— 本机 Node v22.23.1 复现 #1575 ⑤:开满 --experimental-permission --allow-fs-read/--allow-fs-write 的进程里 fetch() 照样 200。因此 tests/execute-script-isolation.test.ts 里留了一条方向反直觉的断言钉死这件事(默认跳过,需显式开 SKILL_SANDBOX_ALLOW_EGRESS_PROBE=1),防止日后有人误以为进程层已管住网络、把容器的 network: none 当冗余摘掉。
③ V2-b/V2-CP 用**伴生容器**而非公网地址:打公网会让离线 CI 里的 V2-b 因为「根本没网」而假绿。两条断言同镜像同脚本同断言,唯一变量是 --network none vs --network 。
④ 通信走共享 volume 上的 unix domain socket,不是端口 —— network:none 与「apps/api 要能调用」在 TCP 上直接矛盾;另一条出路(挂 docker socket 起临时容器)等于给沙箱服务宿主 root,为收紧网络而放开更大的逃逸面,净损失。这是比 contract §4 更严的落地细节,不是放宽。
⑤ ⚠ 机制偏离(行为等价):contract §7 字面写「模型带 run_script 工具」,但 ModelCallPort 没有工具调用面 —— ToolDefinition/ToolCallRequest(#725)已在 #741 被显式退役。改为在既有 complete() 面上用提示协议做同一件事(写脚本→执行→失败回喂 exitCode+stderr→上限 3)。见 run-script-with-retries.ts 头注与 #1583,待 review 裁决。
⑥ 预装依赖必须是**扁平真实目录树**(镜像里 npm 装):Node 权限模型不递归解析软链,pnpm 的二层软链布局下把链条上每段真实路径全部授权也依然被拒。另:--allow-fs-read=a,b 逗号形式不会被拆成两个路径,必须每路径一个独立旗标。两条都在代码里留了头注。
⑨ §6.1 转异步落地:新表 `skill_trial_runs`(迁移 20260819120000)+ `FOR UPDATE SKIP LOCKED` 认领 + 提交后 kick + `GET /skill-trial-runs/:id` 轮询。**刻意不复用 `agent_runs`** —— 那张表的 thread_id/input_message_id 是 NOT NULL FK 指向 chat_threads/chat_messages,而试跑既无线程也无消息,复用就得为每次试跑伪造聊天行,那些行会真的出现在用户的对话列表里。复用**形态**,不复用**行**。
⑩ ⚠ 转异步带来一个同步实现里不存在的新失败模式:**失败必须落终态**。失败若不入库,轮询端与「还在跑」无法区分,前端只能一直转圈。因此执行器最外层是catch-all,任何意外都映射成具体码落终态;真库测试对三个失败码各查一遍库。
⑪ `lint-permission-paths` 新增第 67 条豁免(`pg-skill-trial-run-store.ts`):试跑背后没有 ACL 对象,披露规则只有「只有提交者本人能读自己那一行」,由 SQL 谓词 `actor_id = $3` 表达。为过 linter 编一个 ObjectRef 种类会更糟(断言一套并不存在的权限模型)。⚠ 豁免配了机械守卫 `tests/skill/trial-run-store-reads-are-actor-scoped.test.ts`(同 registration-repo-is-write-only 的先例),已实测反证:去掉 `actor_id = $3` 该测试立刻红。

## 设计参照

(无 UI 或沿用现有界面)

## 前置依赖

- `F192` — 已就绪

## 元数据

| phase | sprint | 能力平面 | 优先级 | wave | area |
|---|---|---|---|---|---|
| 01 | 01 | CAP-AI | P1 | - | ai-runtime |

## 开工流程(agent 必读)

> 本 issue 是仓库的**只读投影**;权威是 [`phases/phase-01-run-a-project/feature_list.json`](https://github.com/boardx/workspacex/blob/main/phases/phase-01-run-a-project/feature_list.json)。若两者不一致,以仓库为准。

1. 环境:`./init.sh`(验证失败先修基础状态,别在坏地基上开工)。
2. 认领:`pnpm harness claim --phase 01 --feature F962 --owner <你的-agent-id>`(同一 owner 同时最多一个 in_progress)。
3. 读上下文:[`requirements/`](https://github.com/boardx/workspacex/blob/main/phases/phase-01-run-a-project/requirements)(原始需求)、[`contracts/`](https://github.com/boardx/workspacex/blob/main/phases/phase-01-run-a-project/contracts)(本 feature 所属契约束:`ui.md` 给组件落点与 data-testid,`usecases.md` 给失败模式,`design-signoff.md` 给签核状态)、[`sprints/sprint-01/session-handoff.md`](https://github.com/boardx/workspacex/blob/main/phases/phase-01-run-a-project/sprints/sprint-01/session-handoff.md)(上一轮交接)。
4. 实现:只做本 feature 的最小实现,不顺手重构无关区域;不碰 `active-features.json`(脚本派生只读)。
5. 验证:逐条跑上方 verification,输出留到 `phases/phase-01-run-a-project/sprints/sprint-01/evidence/F962.verify.log`;然后 `pnpm harness verify --sprint 01/01 --feature F962` 门控转 passing——**不允许手改 status**。
6. 提交:分支 `worker/-01-f962-`,PR 关联本 issue(`Closes #<本 issue 号>`),收尾更新 progress.md 与 session-handoff.md。

完整硬约束见 [`AGENTS.md`](https://github.com/boardx/workspacex/blob/main/AGENTS.md);多 agent 协作规则见 [`.harness/instructions/multi-agent-coordination.md`](https://github.com/boardx/workspacex/blob/main/.harness/instructions/multi-agent-coordination.md)。

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with ./init.sh, then read feature_list.json, the referenced requirements and contracts, and the run-script-with-retries.ts notes. Use the listed sandbox, API, migration, and isolation tests as entry points. Done means every verification command exits 0, evidence is written to phases/phase-01-run-a-project/sprints/sprint-01/evidence/F962.verify.log, and the harness reports F962 as passing.

Written by the indexing model from the issue text.

Assessment

Tech stack
docker, nodejs, typescript
Domain
backend, infrastructure, security, testing-qa
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
32/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.