稳定AI原生壳全量测试
Project CI / Frontend tests (push) Successful in 2m47s
Project CI / Repository checks (push) Successful in 3m6s
Project CI / Backend tests (push) Successful in 10m25s
Project CI / Native shell tests (push) Failing after 15m34s

为 command.exec 沙箱测试安装受信任的 ripgrep
将共享后台锁的 Tauri suite 固定为单线程
同步 CI 调度与排障文档
This commit is contained in:
2026-07-21 22:23:17 +08:00
parent 94ee363d84
commit a273377b17
5 changed files with 5 additions and 4 deletions
@@ -454,7 +454,7 @@ npm run check:server-rs-ddd
- 仓库 CI 入口是 `.gitea/workflows/project-ci.yml`,向 `master`、`codex/ai-game-creator-app` 推送和所有 PR 创建、更新时必须运行,也允许手工触发。
- CI 固定拆分为 `Repository checks`、`Frontend tests`、`Backend tests`、`Native shell tests` 四个 required job;对应 PR context 完整名称是 `Project CI / Repository checks (pull_request)`、`Project CI / Frontend tests (pull_request)`、`Project CI / Backend tests (pull_request)`、`Project CI / Native shell tests (pull_request)`,首次运行后仍须从 Gitea 最近一周 context 表复核。测试使用独立 job,不能只藏在综合检查 step 中;原生壳验收单独运行以便定位重型构建失败。
- 四个 job 共同覆盖 `npm run check`,并追加 `npm run check:server-rs-ddd`、`cargo test --locked --workspace --no-fail-fast --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --all-targets --manifest-path server-rs/Cargo.toml` 和 `cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml`。后端 runner 安装 `ffmpeg`,避免视频抽帧测试因工具缺失提前返回。`codex/ai-game-creator-app` 分支必须用独立 lockfile 安装 AI 游戏创作壳依赖,其原生壳入口还必须覆盖 `npm run ai-game-creator-shell:check`、release build smoke,并检查 AI Tauri `Cargo.lock` 不漂移。原生壳 job 还要把 `actions/setup-node` 的 Node.js 22 与 npm 映射到 `/usr/local/bin`,供只接受受信任系统命令目录的 `command.exec` 沙箱测试使用,不能放宽生产命令目录白名单。
- 四个 job 共同覆盖 `npm run check`,并追加 `npm run check:server-rs-ddd`、`cargo test --locked --workspace --no-fail-fast --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --all-targets --manifest-path server-rs/Cargo.toml` 和 `cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml`。后端 runner 安装 `ffmpeg`,避免视频抽帧测试因工具缺失提前返回。`codex/ai-game-creator-app` 分支必须用独立 lockfile 安装 AI 游戏创作壳依赖,其原生壳入口还必须覆盖 `npm run ai-game-creator-shell:check`、release build smoke,并检查 AI Tauri `Cargo.lock` 不漂移。原生壳 job 还要安装 `ripgrep`,把 `actions/setup-node` 的完整 Node.js 22 发行目录与 root-owned rustup proxy 映射到 `/usr/local`,供只接受受信任系统命令目录的 `command.exec` 沙箱测试使用,不能放宽生产命令目录白名单。Tauri 的 1132 项级别 suite 固定 `--test-threads=1`,避免共享 Agent Runtime 后台锁与异步终态在 libtest 并行调度下互相干扰。
- checkout 必须使用完整历史。PR 将 base SHA 写入 `SPACETIME_SCHEMA_BASE_REF`,直接推送 `master` 使用 before SHA;事件基线不可解析时直接失败。Gitea 检查的是 PR head 而非预合并 commit,workflow 必须拒绝不包含最新 base commit 的过期 PR,分支保护同时保持“PR 过期禁止合并”。
- 普通 PR job 不读取业务 secret,不运行真实 API/SpacetimeDB/OSS/支付/生成/live smoke,也不执行会修改外部状态的维护、迁移、发布或备份命令。
- Gitea 至少升级到 `1.26.4` 后才能注册执行 PR job 的 runner;`ubuntu-latest` 标签只映射到固定 digest 的 Ubuntu 24.04 级 Docker/临时隔离镜像,不使用浮动镜像 tag,不映射 host,不向 job 暴露 Docker socket、业务 secret 或不必要内网。runner 能访问 Gitea、GitHub Actions 与 `actions/node-versions`、nodejs.org、npm、Rust 分发和 crates.io;workflow 的官方 action 固定完整 commit,若内网禁用 GitHub,先在当前 Gitea 镜像对应 commit 并改用绝对 URL。受控镜像优先预装 rustup。Gitea 1.26 的任务超时由 runner 全局配置控制;首次运行成功后,`master` 分支保护必须要求上述四个 job 全部成功。
@@ -3482,5 +3482,5 @@
- 原因:空 `docker_host` 会自动发现并把控制 socket 传播到 job;rootful Docker 的 seccomp、AppArmor 与 system-path masks 又不支持完整 nested bwrap。Runner 2.0.0 还有一处独立合并缺陷:`parseSystemPaths` 把 `systempaths=unconfined` 转成显式空 slice 后,`mergo.WithOverride` 不覆盖 empty value,真实 job 又恢复 Docker 默认 masks,表现为配置文件写了 unconfined、手工 `docker run` canary 也成功,但 Actions job 仍在 `--proc /proc` 返回 EPERM。直接使用 `--privileged`、外层 `CAP_SYS_ADMIN` 或 host executor 会把测试跑绿建立在破坏 PR 隔离的前提上。宿主启用 Clash fake-IP 时,简单按 DNS 的 `198.18.0.0/15` 判断公网还会误拒所有公共依赖。
- 处理:先把 Gitea 升到至少 `1.26.4`,再用固定 digest 的 Runner 2.0.0 rootless DinD;外层保持非 privileged、无 `CAP_SYS_ADMIN`,内部 Docker 仅 Unix socket,runner 固定 `docker_host: "-"`。若版本仍有上述 empty-slice 缺陷,只做 merge 后保留 `MaskedPaths=[]` / `ReadonlyPaths=[]` 的最小补丁并固定自有镜像 digest,不对整个 HostConfig 启用 overwrite-empty。job 使用独立 internal network,Gitea 经 reverse gateway,公共 80/443 经使用公共 DoH 验证真实 IP、拒绝私网/保留地址/metadata 的 proxy;DoH 要缓存并合并同域并发,长下载 timeout 不能只有 60 秒。rootless job 内的 bwrap namespace/proc 选项不能复制到宿主 rootful runner。apt 步骤在 root 时直接执行,非 root 时用 `sudo -E`,否则 sudo `env_reset` 会让 apt 丢失 proxy。
- 运维陷阱:从容器内运行 Compose 时,宿主 `/opt/gitea-stack` 必须挂到容器同名绝对路径;挂成 `/stack` 会让相对 bind source 被 daemon解析为宿主 `/stack/...`,表现为 Gitea进入空安装页、gateway 脚本“缺失”。发现后不要迁移空库,立即用同路径 mount 重建并核对原数据大小、installed 日志、仓库数和 API。切换前保留冷数据 tar、pg_dumpall 和原 compose/env/runner 配置,备份与 token 不提交 Git。
- 验证:同时检查 Gitea 版本、runner declare、外层 `Privileged=false`/无 CapAdd/无宿主 socket、inner job `Binds=[]`、`MaskedPaths=[]`、`ReadonlyPaths=[]`、固定 image digest、`/var/run/docker.sock` 不存在、公共 proxy 可用、直连公网/Postgres/metadata 失败,以及完整 bwrap canary。最后重跑四个 CI job;checkout 成功但 apt/rustup/npm 同时失败时,先排 proxy/env,而不是改测试。
- 验证:同时检查 Gitea 版本、runner declare、外层 `Privileged=false`/无 CapAdd/无宿主 socket、inner job `Binds=[]`、`MaskedPaths=[]`、`ReadonlyPaths=[]`、固定 image digest、`/var/run/docker.sock` 不存在、公共 proxy 可用、直连公网/Postgres/metadata 失败,以及完整 bwrap canary。AI 原生壳的共享 Agent Runtime 后台锁 suite 固定单线程执行;并行全量出现锁或异步终态失败、逐项单线程全部通过时,修正 suite 调度口径,不放宽断言。最后重跑四个 CI job;checkout 成功但 apt/rustup/npm 同时失败时,先排 proxy/env,而不是改测试。
- 关联:`.gitea/workflows/project-ci.yml`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、`docs/project-memory/shared-memory/development-workflow.md`。