proof — 一段未经剪辑的变更旅程视频
审阅者应该能够以正常速度,从头到尾观看用户所看到的内容。视频就是证据;截图、联系表或快进录屏都不算。
关于不同 Tier 需要什么的权威来源:docs/process/PROOF-POLICY.yml(T0 无;T1 聚焦的测试 + 类型检查;T2 在模拟世界上添加一段桌面端变更旅程的视频,如果布局改变了还要加手机端;T3/T4 增加更多)。通过 node tools/proof/tier.mjs --base origin/main 获取当前差异的 Tier。
规则
- 未经剪辑且实时速度。 一个浏览器上下文,一次录制。绝不要修剪、拼接、加速或减速。如果旅程时长超过策略中的
max_seconds,请录制一个仍能展示变更但更短的旅程,而不是一个更快的旅程。 - 录制从到达(arrival)开始(默认):录制在世界显示在屏幕上且到达卡片消失后开始,因此视频片段从世界画面开始,而不是加载画面。从那里开始,是一次未经剪辑、实时速度的旅程录制,这正是策略所要求的。当加载或到达本身就是变更的内容(例如到达卡片、加载屏幕、首屏绘制),或者旅程不在世界页面上(世界列表、登录页)时,请传入
--from-load:此时录制从页面创建开始。侧边栏中的start(arrival或load)说明了使用的是哪种方式,并且upload.mjs --proof会将其复制到 proof.json 的media[]中。 - 白天时间。 在本地世界 URL 上,录制器会在页面打开前将世界时间设置为正午并暂停时钟(
--time-of-day 12,默认值;作为开发所有者使用的 god 环境路由)。开发世界在 06:00 启动,并以每小时一个世界日的速度运行,因此没有这个设置的话,一个运行了 40 分钟的世界会是夜晚。此设置会在世界的 god 历史中添加一个world条目,之后时钟保持暂停。如果时钟、灯光或 god 历史本身就是变更的内容,可以传入另一个小时数或--time-of-day keep。侧边栏的worldTime会说明设置的值。 - 真实输入。 使用
page.mouse、page.keyboard、page.touchscreen或定位器点击来驱动,像用户一样操作。不要在摄像头上通过page.evaluate调用应用内部方法来推动事件。 - 模拟大脑,隔离数据。 仅限本地世界,
BRAIN_MODE=fake,使用全新的数据目录。绝不要对着生产世界录制,除非维护者要求,也绝不要使用已登录的个人浏览器配置文件。 - 绝不要将视频(或帧)提交到仓库中。在
.e2e/(已被 gitignore 忽略)下工作。 - 视频片段必须展示变更内容。在附加它之前,请观看它(或至少提取帧,见下文)。
1. 启动一个模拟大脑世界
需要一个构建好的 Web 客户端(pnpm install --frozen-lockfile && pnpm build;如果 apps/web/dist/index.html 已存在且对你的变更为最新,可跳过)。在新的克隆中,构建的预检检查会失败,直到 Web 媒体资源就位:node tools/assets/pull.mjs --only 'apps/web/**'(几个文件)就足够了。
node tools/god-skill/dev-world.mjs proof 4710 --genesis worlds/templates/hello-hamlet.json --fresh --data .local/proof
# 输出 http://127.0.0.1:4710/w/proof?dev=1 (pid N, log .lane/proof.log); 服务器保持后台运行
首页(世界列表,新建世界)由世界启动器提供服务,而不是由单个世界。对于从那里开始的旅程,请运行一个启动器(其世界总是获得模拟大脑)并通过其 API 创建一个世界以显示在列表中:
node tools/world-launcher/launcher.mjs --port 4720 --range 4721-4723 --data "$PWD/.local/proof-launcher" --eager 0 &
curl -s -X POST -H 'content-type: application/json' -H 'authorization: Bearer dev:proof' \
-d '{"name":"Proof Garden","template":"hello-hamlet"}' http://127.0.0.1:4720/__sim/worlds
# 对着 http://127.0.0.1:4720/ 录制;通过 PID 停止启动器(它会停止其所有世界)
选择包含变更界面的模板(worlds/templates/:hello-hamlet 小而快;eden-2026、mars-two 等更大)。使用空闲端口;e2e/config.mjs 列出了为其他测试工具预留的端口。完成后使用 kill <pid> 停止服务器。
快速验证应用是否正常运行:本地烟雾测试
在录制之前,或者作为 T1 变更(涉及服务器或 Web 应用)的整个证明,请运行本地烟雾测试。它会启动自己的隔离启动器(模拟大脑、开发登录、数据在运行目录下)在一个空闲端口上,打开世界列表和一个世界,检查一个人物是否被绘制且没有控制台错误和失败的请求,然后停止它启动的一切:
node e2e/local-smoke.mjs --port 9950 --clip # 在新克隆上先 --build;省略 --port 以使用任意空闲端口
# → .local/e2e/<timestamp>/summary.json, shots/1-world-list.png 2-world.png 3-person.png, clip.mp4
node tools/proof/upload.mjs --pr $N .local/e2e/<timestamp>/summary.json .local/e2e/<timestamp>/shots/*.png
summary.json 中的 status: "pass" 就是证据;其视频片段是一段未经剪辑、实时速度的烟雾测试旅程。它不是你变更的视频片段:T2 变更仍然需要录制自己的旅程,见下文。详细信息:docs/testing/e2e.md#local-smoke。
2. 编写步骤
将 .claude/skills/proof/steps.example.mjs 复制到 .e2e/proof/<slug>/steps.mjs 并编写变更旅程的脚本:执行操作,展示结果。在默认的到达(arrival)启动模式下,步骤从世界到达后开始(arrived 标记为 0:00);使用 --from-load 时,步骤在导航后立即开始。mark('label') 会将章节时间戳写入侧边栏,这样 PR 上就可以说“面板在 0:07 打开”。请只编写变更所影响的旅程。
3. 录制
node .claude/skills/proof/record.mjs --url 'http://127.0.0.1:4710/w/proof?dev=1' \
--steps .e2e/proof/<slug>/steps.mjs --out .e2e/proof/<slug>/desktop.mp4 --viewport desktop
# 布局或触摸有变化?也录制:
node .claude/skills/proof/record.mjs --url '...' --steps .e2e/proof/<slug>/steps.mjs \
--out .e2e/proof/<slug>/phone.mp4 --viewport phone
- 浏览器:使用
PLAYWRIGHT_BROWSERS_PATH,如果存在则使用/opt/pw-browsers。如果 Chromium 缺失:PLAYWRIGHT_BROWSERS_PATH="$PWD/.e2e/browsers" pnpm --filter mars-campus-viewer exec playwright install chromium。 - Playwright 的解析方式与 playtest 测试工具相同(
tools/playtest/lib/platform.mjs:全局的playwright,否则是mars/viewer中的@playwright/test)。不需要 GPU;WebGL 在 SwiftShader 上运行。 --max-seconds默认为策略中的max_seconds。退出码 3 表示旅程运行超时:请缩短它。- 使用 ffmpeg:会生成一个 mp4 文件,底部有进度条和
hh:mm:ss / total时间码(时间码需要带有 drawtext 的 ffmpeg;侧边栏的timecode会说明时间码是否存在)。没有 ffmpeg:生成原始的.webm文件;在 PR 中说明(播放器仍然显示自己的进度条)。 - 每个视频片段都会产生一个
<clip>.json侧边栏:包含 URL、视口、时长、start、startedAt、标记(包括arrived)、worldTime、控制台错误、提交和脏标记。如果 Playwright 没有page.screencast,即使没有--from-load,也会从页面创建开始录制;侧边栏会在startFallback中说明。 - 页面有
--arrival-seconds(默认 120)在录制开始前等待世界到达;从未显示世界的页面会失败(退出码 1),并提示使用--from-load,同时生成一个静止图像(<clip>.open-failed.png)和一个侧边栏。侧边栏中的控制台错误是需要分析发现的问题:请解释或修复它们。
录制运行时间远低于 10 分钟的前台限制。如果需要一次录制多个视频,可以在后台运行它们(nohup setsid node ... > .e2e/proof/<slug>/rec.log 2>&1 &)并轮询日志。
4. 亲自检查
for t in 1 4 8; do ffmpeg -loglevel error -y -ss $t -i .e2e/proof/<slug>/desktop.mp4 -frames:v 1 .e2e/proof/<slug>/t$t.png; done
查看这些帧(阅读 PNG 文件)。变更的状态必须可见,世界必须已加载(不是空白或加载中的画布),并且帧内不应包含任何私人信息。如果视频没有展示变更,请修复步骤并重新录制;不要附加一个不能证明所声称内容的视频。
5. 附加到 PR(绝不提交到仓库)
在发布前,使用 node tools/proof/publication.mjs <payload files> 检查每个外发的文本/JSON 负载。单独检查媒体像素、音频、文件名和二进制元数据。设备路径、主机名、设备/私有 IP、本地 URL 和操作标识符只属于私有记录。使用相对路径和中性的捕获标签。上传器在上传批次前会阻止检测到的文本泄露;这不能保证媒体隐私。
node tools/proof/upload.mjs --pr $N --proof .e2e/journal/$ISSUE/proof.json .e2e/proof/$SLUG/desktop.mp4 .e2e/proof/$SLUG/phone.mp4
- 默认目标是 PR 自己的预发布版本
proofs-<n>(在默认分支上的标签,首次使用时创建);每个 PR 一个发布版本可以保持每个发布版本在 GitHub 的 1000 个资产限制之内,而旧的共享proofs发布版本已经达到了这个限制。--release <tag>(或SIM_PROOF_RELEASE)可以指定另一个发布版本。资产被命名为pr-<n>.<file>,重新录制的版本会得到-2、-3后缀,这样早期的录制版本(包括失败的)仍然可以链接。SIM_PROOF_UPLOAD_URL(+SIM_PROOF_UPLOAD_TOKEN)会切换到另一个端点,例如 CDN。 - 使用
--proof时,视频片段和图片链接会被写入 proof.json 的media[],其他文件(日志、JSON)写入files[],并且 proof.json 最后上传。将它的链接和视频片段链接粘贴到 PR 的 Proof 部分。journal技能涵盖了 proof.json。 - 内联预览。 GitHub 将发布版本资产作为下载提供,因此 mp4 链接永远不会在 PR 中播放。对于每个视频,
upload.mjs会使用 ffmpeg 制作一个小的动画 GIF(<clip>.preview.gif:从视频片段的arrived标记处,或对于--from-load录制从 0 开始,以实时速度播放 6 秒;最大 5 MB),将其与视频片段并排上传,记录为media[].preview,并输出类似于[](…pr-412.desktop.mp4)的行。粘贴该行:预览图会在 PR 中播放,点击它会打开完整的视频。当变更在视频后续部分才展示时,可以使用--preview-start <s>(和--preview-seconds <s>)选择窗口。预览是指针,不是证据:未经剪辑的 mp4 才是。在每次预览前,始终放置一个明确的全分辨率未经剪辑视频链接,说明实际编码的尺寸和时长。保持捕获分辨率并框定动作,以便可以审查接触点。不要放大捕获画面或使用缩小的 GIF 来声称运动证据。 - 退出码 3:上传被阻止(没有
gh,未登录,没有权限,或者没有网络,许多云会话中常见)。告知用户哪些视频在等待以及它们的位置。发布一个 PR 评论,包含每个视频的时长、视口、经过清理的侧边栏摘要(标记、控制台错误、提交),以及一个仓库相对路径的record.mjs命令和步骤文件,以便任何人都可以重新录制它。在 PR 正文中将证明标记为“pending upload”。绝不要将视频作为 base64 粘贴,绝不要提交它,也绝不要将其推送到另一个分支。
gh pr 在云会话中可能不起作用;请使用 gh api repos/$REPO/issues/$N/comments -F [email protected]/proof/$SLUG/comment.md 发表评论。
在视频旁边写什么
每个视频一行:是什么旅程,哪个世界模板,视口,时长,提交,以及变更可见的时刻(“0:07 座位高亮显示,Soul 坐下”)。加上任何控制台错误以及它们为什么是可以接受的。
