完成 Editor Agent Mock Agent P1 收尾
接入 Web Project 契约、SpacetimeDB 表与 api-server 控制面 新增 Mock Agent、静态构建 runner 与独立预览网关 补齐 /editor/agent 前端页面、服务客户端和 SSE 订阅 修复 sandbox 预览资源跨域加载并补充并发保护 接入本地 dev 预览端口漂移与服务身份初始化 更新 P1 技术方案、验收清单和 Hermes 共享记忆
This commit is contained in:
@@ -110,6 +110,7 @@ P1 字段建议:
|
||||
- 拒绝符号链接语义。
|
||||
- 限制目录深度、单文件大小、snapshot 总大小。
|
||||
- P1 只允许文本源码和少量静态资源。
|
||||
- P1 可编辑范围限定为 `src/App.tsx`、`src/App.css`、`src/components/**`、`src/assets/**` 与 `public/**`;固定模板控制文件 `index.html`、`src/main.tsx`、`tsconfig.json`、`vite.config.ts` 不进入用户 snapshot。
|
||||
- `package.json` 不是事实源;新增依赖和 scripts 在 P1 拒绝或忽略。
|
||||
|
||||
## 后端存储
|
||||
@@ -120,6 +121,7 @@ P1 新增 SpacetimeDB 表:
|
||||
web_project
|
||||
web_project_snapshot
|
||||
web_project_preview_build
|
||||
web_project_service_identity
|
||||
```
|
||||
|
||||
`web_project`:
|
||||
@@ -162,6 +164,42 @@ web_project_preview_build
|
||||
- `finished_at`
|
||||
- `updated_at`
|
||||
|
||||
`web_project_service_identity`:
|
||||
|
||||
- `service_identity`
|
||||
- `created_at`
|
||||
- `created_by`
|
||||
- `note`
|
||||
|
||||
`web_project` procedure 入口必须先校验 SpacetimeDB `ctx.sender()` 命中 `web_project_service_identity`,再信任 api-server 从 `AuthenticatedAccessToken` 派生并代传的业务 `owner_user_id`。该表是运行环境服务授权表,不作为普通业务迁移数据跨环境导入导出。
|
||||
|
||||
### `web_project_service_identity` 授权流程
|
||||
|
||||
`web_project_service_identity` 是环境级 allowlist,不随 `migration.rs` 普通业务迁移导入导出。首个服务身份需要使用构建 `spacetime-module` wasm 时注入的 `GENARRATIVE_SPACETIME_WEB_PROJECT_SERVICE_BOOTSTRAP_SECRET` 完成引导授权;密钥只通过本机或生产 Secret 管理系统注入,不写入 Git、文档、日志或 issue。
|
||||
|
||||
本地开发流程:
|
||||
|
||||
1. 默认使用 `npm run dev`:脚本会为本地 `spacetime-module` 构建注入一次随机 Web Project 服务身份引导密钥,复用 / 创建本地 api-server SpacetimeDB Web identity,并在启动 api-server 前通过 HTTP procedure 自动写入 `web_project_service_identity` allowlist。identity / token 缓存在 gitignored 的本机 SpacetimeDB data dir 下,避免每次启动产生新服务身份。
|
||||
2. 若使用 `--skip-publish`、单独启动 `npm run dev:api-server` 或排查特殊数据库,需要先确认当前发布的 wasm 已通过 `GENARRATIVE_SPACETIME_WEB_PROJECT_SERVICE_BOOTSTRAP_SECRET` 注入引导密钥,再用显式 `--server` 调用授权 procedure。`<database>`、`<serviceIdentityHex>` 和 `<bootstrapSecret>` 均替换为本机实际值;不要把命令历史或输出中的 secret 贴到共享文档:
|
||||
|
||||
```bash
|
||||
spacetime call <database> authorize_web_project_service_identity '{"bootstrap_secret":"<bootstrapSecret>","service_identity_hex":"<serviceIdentityHex>","note":"local api-server web project"}' --server http://127.0.0.1:3101
|
||||
```
|
||||
|
||||
3. 首个身份写入后,后续新增 / 轮换服务身份应由已授权服务 identity 调用同一 procedure;此时 `bootstrap_secret` 可传空字符串或占位值,procedure 会改为校验 `ctx.sender()` 是否已经在 allowlist 内。
|
||||
4. 如需撤销旧身份,用已授权服务 identity 调用:
|
||||
|
||||
```bash
|
||||
spacetime call <database> revoke_web_project_service_identity '{"service_identity_hex":"<oldServiceIdentityHex>"}' --server http://127.0.0.1:3101
|
||||
```
|
||||
|
||||
生产 / 预发 ops 口径:
|
||||
|
||||
1. `Genarrative-Stdb-Module-Publish` 或等价发布流程必须在构建 `spacetime-module` wasm 时从 Jenkins Secret Text / 服务器 Secret Store 注入 `GENARRATIVE_SPACETIME_WEB_PROJECT_SERVICE_BOOTSTRAP_SECRET`;生产文档和流水线日志只允许记录“已注入 / 长度满足要求”,不得输出明文。
|
||||
2. 发布后由 ops 在目标 SpacetimeDB server 上显式指定 `--server` 执行一次 `spacetime call <database> authorize_web_project_service_identity ...`,把生产 `api-server` 使用的 SpacetimeDB identity 加入 allowlist。该步骤尚未在本轮验证完成,不能视为 P1 已生产初始化。
|
||||
3. 首个生产身份授权完成后,应从运行环境移除或轮换 bootstrap secret;后续服务身份轮换只允许通过已授权身份执行 `authorize_web_project_service_identity` / `revoke_web_project_service_identity`,不再依赖 bootstrap secret 扩权。
|
||||
4. 授权失败时优先排查:目标 server / database 是否正确、CLI 当前 token 对应 identity 是否符合首个引导或已授权身份、bootstrap secret 是否注入到发布后的 wasm、`service_identity_hex` 是否为 64 位十六进制 SpacetimeDB identity。不得使用旧 `maincloud` 口径或 `spacetime --root-dir` 手工绕过。
|
||||
|
||||
落点:
|
||||
|
||||
- `server-rs/crates/spacetime-module/src/web_project.rs`
|
||||
@@ -262,7 +300,7 @@ P1 至少支持以下 mock 指令族:
|
||||
- `计数` / `按钮`:生成带计数按钮的 React 页面。
|
||||
- `卡片` / `列表`:生成卡片列表页面。
|
||||
- `蓝色` / `绿色` / `粉色`:调整 CSS 主题色。
|
||||
- `破坏构建`:生成一个 TypeScript 编译错误,用于验收失败保留上一版预览。
|
||||
- `破坏构建`:生成一个 TSX 语法构建错误,用于验收失败保留上一版预览;P1 runner 只执行 `vite build`,不把 TypeScript 类型检查作为失败路径依据。
|
||||
- 其它输入:只更新标题和说明文案,避免 mock 分支过多。
|
||||
|
||||
mock Agent 输出仍必须经过统一 `validate_web_project_patch(...)`,不得直接写表。
|
||||
@@ -300,17 +338,21 @@ P1 runner 步骤:
|
||||
9. 失败时返回错误摘要和日志片段,日志需脱敏并避免完整宿主路径。
|
||||
10. 清理临时目录。
|
||||
|
||||
P1 允许的构建命令只能来自平台常量,使用 argv 方式执行,不经 shell、不拼接字符串、不执行用户传入命令:
|
||||
P1 允许的构建命令只能来自平台常量,不拼接用户字符串、不执行用户传入命令;Windows 下 npm 通过 `cmd.exe /d /s /c npm.cmd ...` 启动以兼容 `.cmd` shim,其余平台直接执行 `npm`:
|
||||
|
||||
```text
|
||||
npm ci --ignore-scripts --offline --no-audit --fund=false
|
||||
npm exec --offline -- vite build
|
||||
```
|
||||
|
||||
固定 Vite 模板必须设置 `base: './'`,确保独立 preview gateway 的 `/p/<previewToken>/` 子路径能够加载 `assets/**`。
|
||||
|
||||
如果离线缓存不足,P1 只能改为访问平台受控 npm registry mirror,并由 runner 写入受控 `.npmrc`;用户 snapshot 中的 `.npmrc`、registry、proxy、`package-lock.json` 篡改和新增依赖必须拒绝或重写。禁止为了开发速度复用当前仓库的 `node_modules`、源码目录或本机全局 npm cache 作为 runner 工作区输入。
|
||||
|
||||
P1 默认网络策略为 deny all。仅当使用平台受控 registry mirror 或资产只读签名域时开放精确白名单;必须阻断 RFC1918 内网、云 metadata 地址、api-server 管理端口、SpacetimeDB、生产数据库、Docker daemon,并覆盖 HTTP redirect 和 DNS rebinding 到内网的情况。
|
||||
|
||||
preview build 需要双层并发保护:全局最多同时运行 `GENARRATIVE_WEB_PROJECT_PREVIEW_BUILD_MAX_CONCURRENT_TASKS` 个构建任务,默认值为 `2`;同一个 project 只能同时占用一个 runner slot。全局 slot 不足时 api-server 返回 `429 TOO_MANY_REQUESTS`,避免多个预览任务一起挤爆本地构建资源。
|
||||
|
||||
## Preview Gateway
|
||||
|
||||
P1 开发态 preview 可以使用独立端口:
|
||||
|
||||
61
docs/technical/【技术方案】WebProjectRunnerP1实现补充-2026-06-15.md
Normal file
61
docs/technical/【技术方案】WebProjectRunnerP1实现补充-2026-06-15.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# WebProject Runner P1 实现补充
|
||||
|
||||
更新时间:`2026-06-16`
|
||||
|
||||
本文补充 `server-rs/crates/web-project-runner` 的 P1 实现口径,主约束仍以 [`【技术方案】EditorAgentMockAgentP1落地计划-2026-06-15.md`](./【技术方案】EditorAgentMockAgentP1落地计划-2026-06-15.md)、[`【安全模型】AIWeb工程Runner与预览隔离威胁模型-2026-06-13.md`](./【安全模型】AIWeb工程Runner与预览隔离威胁模型-2026-06-13.md) 和 [`【测试用例】AIWeb工程静态预览MVP验收清单-2026-06-13.md`](./【测试用例】AIWeb工程静态预览MVP验收清单-2026-06-13.md) 为准。
|
||||
|
||||
## 当前实现边界
|
||||
|
||||
- `web-project-runner` 是独立 workspace crate,提供 `run_web_project_build(input) -> output` 库 API。
|
||||
- 输入只接受 `jobId`、`projectId`、`snapshotId`、`files` 和服务侧配置传入的 `artifactRoot`。
|
||||
- 输入不接受用户命令、构建参数、工作区路径或 artifact 输出路径。
|
||||
- 每个 job 创建任务级临时目录,先写入 runner 受控 React / Vite / TypeScript 模板,再写入 snapshot 文件。
|
||||
- snapshot 路径必须是相对路径,拒绝绝对路径、`..`、`.env`、`.npmrc`、`.git`、`.ssh`、`package-lock.json` 和超限文件。
|
||||
- snapshot 只能覆盖 P1 可编辑文件:`src/App.tsx`、`src/App.css`、`src/components/**`、`src/assets/**` 与 `public/**` 下的受控静态文本资源;`index.html`、`src/main.tsx`、`tsconfig.json`、`vite.config.ts` 等固定模板控制文件不得由 snapshot 覆盖。
|
||||
- snapshot 中的 `package.json` 不作为依赖事实源;P1 拒绝其中的 scripts、dependencies、devDependencies、registry、workspace 等供应链字段。
|
||||
- Preview token 使用 `wpt_<issuedMicros>_<randomSecret>` 不透明票据,gateway 只按完整 token 回查 DB 中保存的 `preview_token_id`,并校验 24 小时 TTL;URL token 不承载可信 owner 或 job 信息。
|
||||
- Preview gateway 的 CSP 保持 `connect-src 'none'`、`worker-src 'none'`、`object-src 'none'` 和 `no-store`;`frame-ancestors` 由 `GENARRATIVE_WEB_PROJECT_PREVIEW_FRAME_ANCESTORS` 配置允许平台编辑器宿主嵌入,开发默认允许 `http://127.0.0.1:3000 http://localhost:3000`,生产必须显式配置主站 origin。
|
||||
- 构建命令固定为 argv 常量:
|
||||
- `npm ci --ignore-scripts --offline --no-audit --fund=false`
|
||||
- `npm exec --offline -- vite build`
|
||||
- Windows 下 runner 仍以固定命令语义执行 `npm`,实际进程层通过 `cmd.exe /d /s /c npm.cmd ...` 启动,避免直接 spawn `.cmd` 失败;其它平台直接执行 `npm`。
|
||||
- 子进程使用环境变量白名单,不继承平台密钥和 `.env`;Windows 仅额外保留 Node / npm 启动所需的 `SystemRoot`、`ComSpec`、`TEMP` 等系统变量。
|
||||
- 构建成功后只复制 `dist/` 到 runner 计算出的 immutable artifact 目录;失败返回结构化 `errorSummary` 和日志片段。
|
||||
- runner 会清理任务级临时目录;preview gateway 不应服务 runner 临时工作区。
|
||||
- 固定 Vite 模板设置 `base: './'`,确保 preview gateway 的 `/p/<previewToken>/` 子路径可以加载 `assets/**`。
|
||||
|
||||
## 依赖缓存口径
|
||||
|
||||
runner 内置 `templates/react-vite-ts-static/package.json` 和 `package-lock.json`。模板 lockfile 必须在模板目录内用 npm 生成,不能手工从仓库根 lockfile 裁剪依赖闭包;当前 `vite@6.4.1` 对应的嵌套 `esbuild` 锁定为 `0.25.12`。
|
||||
|
||||
更新模板依赖或 lockfile 时使用:
|
||||
|
||||
```powershell
|
||||
Push-Location server-rs/crates/web-project-runner/templates/react-vite-ts-static
|
||||
npm install --package-lock-only --ignore-scripts --no-audit --fund=false --registry=https://registry.npmjs.org
|
||||
Pop-Location
|
||||
```
|
||||
|
||||
本地运行态 smoke 前如果 npm 离线缓存未预热,可在模板目录或临时目录执行一次非 offline 的 `npm ci --ignore-scripts --no-audit --fund=false` 预热缓存;runner 本身仍必须使用 `--offline`。
|
||||
|
||||
P1 不为了本地构建成功放开网络、scripts、用户 registry 或用户 lockfile。如果机器离线 npm cache 未预热,`npm ci --offline` 可以失败,runner 应返回 `failed` 和可读错误摘要。后续要提升成功率,应通过平台受控模板缓存预热或受控 npm mirror 解决,不允许从请求体传入 registry、proxy 或替换构建命令。
|
||||
|
||||
## 已覆盖测试
|
||||
|
||||
本轮 P1 主线已确认 `cargo test -p web-project-runner --manifest-path server-rs/Cargo.toml` 通过,覆盖:
|
||||
|
||||
- 路径逃逸和敏感文件拒绝。
|
||||
- 普通源码相对路径通过。
|
||||
- `package.json` 新增依赖、脚本和 package manager 字段被拒绝。
|
||||
- 固定模板控制文件 snapshot 在执行任何命令前被拒绝。
|
||||
- Windows npm 启动路径和系统环境白名单。
|
||||
- 构建失败返回结构化错误,不生成 artifact。
|
||||
- 成功路径写入模板、固定命令 argv 并复制 `dist/` 到 immutable artifact 目录。
|
||||
- 危险 snapshot 在执行任何命令前被拒绝。
|
||||
|
||||
## 后续接入提醒
|
||||
|
||||
- api-server 只能调用 runner API 或触发独立 runner 执行面,不应在 handler 中自行执行 `npm` / `vite`。
|
||||
- api-server 传入的 `artifactRoot` 必须来自服务配置,不得来自用户请求。
|
||||
- runner 当前是 P1 一次性构建形态;持久队列、lease、取消、资源 cgroup、构建期网络 deny-all 和子进程资源隔离仍属于后续切片或部署层门禁。
|
||||
- 2026-06-16 已完成本地运行态 API / preview gateway smoke:`/healthz` 200,`做一个蓝色计数按钮页面` 构建成功并产出 preview URL,`破坏构建` 返回 failed 且上一版 preview 仍可访问。生产 `web_project_service_identity` 授权初始化、真实浏览器点击 smoke 和部署层网络 / Docker socket 门禁仍按 P1 计划保留。
|
||||
@@ -41,6 +41,8 @@ MVP 不支持:
|
||||
- [ ] AI patch plan 新增或修改一个 React 组件后,api-server 校验通过并生成新 snapshot。
|
||||
- [ ] patch 校验失败时不生成 snapshot,不创建 build job,并在聊天中展示可读校验错误。
|
||||
- [ ] 前端 debounce 后创建 preview build job。
|
||||
- [ ] 预览 build 全局并发上限生效,超限时返回 `429 TOO_MANY_REQUESTS`。
|
||||
- [ ] 同一个 project 不会同时跑多个 runner 构建。
|
||||
- [ ] runner 构建成功并产出 immutable artifact。
|
||||
- [ ] SSE 返回 `queued -> running -> succeeded`。
|
||||
- [ ] iframe 切换到新 preview URL。
|
||||
@@ -74,6 +76,7 @@ MVP 不支持:
|
||||
- [ ] 大 Data URL 被拒绝或转资产流程。
|
||||
- [ ] 二进制膨胀被拒绝。
|
||||
- [ ] rename 后目标路径仍需重新校验。
|
||||
- [ ] rename 目标路径已存在时返回 `400`,不静默覆盖。
|
||||
|
||||
## 构建与状态机
|
||||
|
||||
@@ -164,6 +167,8 @@ MVP 不支持:
|
||||
- [ ] iframe sandbox 不允许 camera / mic。
|
||||
- [ ] CSP 禁止未白名单 `connect-src`。
|
||||
- [ ] Service Worker 被禁用。
|
||||
- [ ] iframe sandbox 只保留 `allow-scripts`。
|
||||
- [ ] preview gateway 的 JS / CSS / 资源响应带 `Access-Control-Allow-Origin: *`,可在 sandbox opaque origin 下正常加载。
|
||||
|
||||
## 日志与错误
|
||||
|
||||
@@ -220,6 +225,7 @@ git diff --check
|
||||
提交一次 AI patch
|
||||
等待静态构建成功
|
||||
确认 iframe 展示新预览
|
||||
点击预览中的计数按钮,文案从 `已点击 0 次` 变为 `已点击 1 次`
|
||||
提交一次故意破坏构建的 patch
|
||||
确认错误出现且上一版预览仍保留
|
||||
刷新页面确认项目、日志和 active preview 可恢复
|
||||
|
||||
Reference in New Issue
Block a user