Spike S8 · 实施期 must-have gap 验证(A + B + C)
状态:✅ 全部通过(2026-05-18)
S7 完成后用户 review 时识别出 3 个”实施期 must-have”缺口,本次集中验证:
| 验证项 | 优先级 | 工作量 | |
|---|---|---|---|
| A | OpenSpec CLI 在容器内正确装载 | 🔴 高 | 30 min |
| B | microVM 内 git commit + push 真 PR | 🔴 高 | 1-2 h |
| C | Fly 真启动 microVM(公有云路径) | 🔴 高 | 2-4 h |
A · OpenSpec CLI 容器内装载
问题
S7 时 npm install -g openspec 后 which openspec 不在 PATH,agent 手动 mkdir + mv 模拟 archive 绕开了——但这不能作为生产路径。
根因
npm 上的 openspec 是空占位包:
$ npm view openspec name version
name = 'openspec'
version = '0.0.0' # ← 空包,bin 字段为空真实的 CLI 包是 scoped:
$ npm view @fission-ai/openspec name version bin
name = '@fission-ai/openspec'
version = '1.3.1'
bin = { openspec: 'bin/openspec.js' }主仓库 host 上的 openspec 也是软链到 @fission-ai/openspec:
$ readlink /Users/tanghehui/.nvm/versions/node/v22.22.0/bin/openspec
../lib/node_modules/@fission-ai/openspec/bin/openspec.js验证
box 内(node:22-alpine + apk add ca-certificates bash):
npm install -g @fission-ai/openspec --silent --no-audit
which openspec # /usr/local/bin/openspec
openspec --version # 1.3.1✅ binary 装到 /usr/local/bin/openspec,默认 PATH 可见。
实施期落地
task-runner Dockerfile(或 prep 脚本)必须用正确包名:
# ✗ 错: npm install -g openspec # 装空包
# ✓ 对:
RUN npm install -g @fission-ai/openspecB · microVM 内 git commit + push 真 PR
验证流程
box 内端到端走完整 git workflow + 创 PR:
box (node:22-alpine)
↓ apk add git ca-certs curl
↓ git clone https://x-access-token:$TOKEN@github.com/Xeonice/openspec-spike-poc.git
↓ git checkout -b spike/b-validate-1779091853
↓ printf ">> README.md
↓ git commit
↓ git push origin spike/b-validate-1779091853 ✓
↓ curl POST /repos/.../pulls + Authorization: Bearer $TOKEN
↓ → https://github.com/Xeonice/openspec-spike-poc/pull/2 ✓实际产物:PR #2
关键技巧 · x-access-token URL 注入
git HTTPS clone 的 credential helper 在容器里很复杂(要么 keyring、要么环境变量)。最简单做法:
git clone https://x-access-token:$GH_TOKEN@github.com/owner/repo.git /workspacex-access-token 是 GitHub 的固定用户名占位,密码位放 token——git 自动注入 Authorization header,0 配置。clone 完成后后续 git push 也复用这条 remote。
实施期落地
- 真实场景 token 来源是 GitHub App installation token(D11 已设计),而不是用户 PAT
- token 由主 API 在 dispatch 时调
/internal/tasks/:taskId/token续期接口提供给 box - box 内启动脚本注入
GH_TOKENenv var,task-runner agent 用https://x-access-token:$GH_TOKEN@...clone
用户视图
C · Fly 真启动 microVM
环境
- Fly app:
spike-runner-poc(用户预创建,验证后清理) - token: PAT (
fm2_...) - sandbank:
@sandbank.dev/flyiov0.2.0
实测数据
| 指标 | 值 |
|---|---|
| 启动耗时 | 7.7s · image=node:22-slim · cpu=1 · memory=256MB |
| kernel | Linux 6.12.87-fly x86_64 GNU/Linux |
| CPU | AMD EPYC 2499 MHz (shared) |
| memory | 212 MB(256 配置 - kernel reserve) |
| node | v22.22.3(image 自带) |
| exec round-trip | 0.2-0.5s |
| destroy | 1.6s |
| box → subapi RTT | avg 239ms / median 211ms(公网 HTTPS) |
关键 blocker · 镜像必须自带 bash
Sandbank flyio adapter 内部用 bash -c "<command>" 包装 exec:
// @sandbank.dev/flyio/dist/client.js
async exec(machineId, command) {
return request(`/machines/${machineId}/exec`, {
method: 'POST',
body: JSON.stringify({ cmd: `bash -c ${JSON.stringify(command)}` }),
});
}alpine 默认没装 bash(busybox 提供 sh),首次实验所有 exec 都返回:
stderr: No such file or directory (os error 2)
exit: 0
stdout: (空)切到 node:22-slim(Debian-based 自带 bash)后全部 exec 正常。
公有云 vs 私有路径对比(spike 实测)
| 指标 | BoxLite (macmini Hypervisor) | Fly (AMD EPYC cloud) | 差异 |
|---|---|---|---|
| 启动时间 | 1.5s | 7.7s | 5x(cloud overhead 合理) |
| callback RTT to subapi | 35ms (S6 LAN) | 239ms (公网) | 7x(跨 ocean) |
| capability | exec.stream + sleep + snapshot + … | terminal + volumes + port.expose | Fly 不支持 exec.stream(之前 S2.a 已验) |
| 资源 | 256 MB / aarch64 | 256 MB / x86_64 | 架构不同 |
| 成本(单次任务 ~10 min) | 0(自家硬件) | $0.03-0.05 (shared 1 CPU + 256MB) | — |
→ 印证 D2 双轨设计:
- 私有化路径(BoxLite):硬件门槛低 + 启动快 + LAN 内 callback 几乎零延迟
- 公有云路径(Fly):弹性好 + 无需自己硬件 + 全球 region 可调
实施期落地
- task-runner D11 选择 provider:dispatch 时按 task config 决定
flyioorboxlite - Fly base image 必须含 bash(
node:22-slim或 Dockerfile 显式apt-get install bash) - Fly machine 启动时间 7-10s,dispatch UI 需要给”准备中”占位状态
- 公网 callback RTT 200-300ms,HTTPS SSE keep-alive 长任务时需要 retry/重连机制
累积新发现(写进 design.md)
1. openspec 包必须用 scoped 名 @fission-ai/openspec
- Dockerfile / box prep 脚本写
npm install -g @fission-ai/openspec - 不要写
npm install -g openspec(空包)
2. git push 用 x-access-token:$TOKEN URL 注入
- 替代复杂 credential helper
- token 由 D11 容器内续期 InstallationsService 续期机制提供
3. Fly base image 必须含 bash
- sandbank flyio 内部
bash -c包装 - 推荐 base:
node:22-slim或 Dockerfile 显式装 bash - 不要用
alpine默认(无 bash)
4. 公有云 vs 私有 callback 延迟差 7x
- 私有 LAN: ~35ms
- 公网 HTTPS: ~200-300ms
- 不影响 task 完成(人类感知阈值 100ms 但 callback 是后台异步)
- 影响”实时进度条”的视觉流畅度——UI 端做平滑
文件存档
/tmp/spike-a-openspec-cli.py(macmini 上 box 内验证 openspec 安装)/tmp/spike-b-git-push.py(box 内端到端 git push + PR)/tmp/openspec-spike-s1/spike-c-fly.mjs(Fly machine 真启动)- spike B 产物:PR #2
- Fly app
spike-runner-poc:machine 已 destroy,app 残留,建议 host 上fly apps destroy spike-runner-poc --yes手动清理
POC 验证阶段宣告完成
8 个 spike 全部通过(S1 / S1.5 / S2 / S3+S4 / S5 / S6 / S7 / S8)。每个 spike 的”未覆盖”项要么已被后续 spike 补齐(S6→S3+S4 出口、S7→S5 长时、S8→S7 缺口),要么显式 defer 到实施期 §7(真 backend 跑通后观察长 SSE 等)。
可以开始 task-runner 真实施了。