内部狗粮运行保障
本目录把 Wave0/Wave1 的开波和收口条件落实为默认拒绝的本地证据门。工具只读取本地 JSON、TSV 和附件并重算结论,不连接 staging,不发告警,不执行停服、删档或回滚,也不包含 Playwright/Puppeteer。浏览器采样、真玩和截图只能由 mini-desktop 上的项目统一入口 /gstack 产生。
1. 固定波次
| 波次 | 创作者 | 玩家 | 连续值班覆盖 |
|---|---|---|---|
| wave0 | 3 | 5 | 从工具固化的 startedAt 起真实经过至少 24 小时 |
| wave1 | 10 | 30 | Wave0 收口后开始,真实经过至少 72 小时 |
初始化只接受这两个名称。startedAt 由工具读取 UTC 当前时间写入,CLI 不提供回填参数。重复初始化会校验既有 manifest.json 后返回,不覆盖任何已填写证据。Wave1 初始化前会重跑 Wave0 收口门,并把 Wave0 的 close-result.json 哈希、最后一份冻结清单哈希和 closedAt 固化进 Wave1 manifest;任一绑定变化、P0、确认发布假阳或超过 4 小时待审积压都会拒绝 Wave1。
deploy/dogfood-ops/init-wave.sh \
--root /受控证据根 \
--wave wave0 \
--rc-commit <commit-pinned-RC的40位哈希>
生成目录中的 JSON 带 _template: true,表示它还不是证据。填写完成后删除该字段。附件统一放在波次目录内的 attachments/;JSON/TSV 只写相对路径。绝对路径、.. 路径、符号链接和不存在的附件都会被拒绝。
2. 执行顺序
开波前按以下顺序执行,任一步非零都保持发布冻结:
# 1. 初始化并填写同一波次的全部结构化证据。
deploy/dogfood-ops/init-wave.sh \
--root /受控证据根 \
--wave wave0 \
--rc-commit <commit-pinned-RC的40位哈希>
# 2. 先跑开波门;模板、缺字段、阈值不达标都返回非零。
deploy/dogfood-ops/check-readiness.sh --wave-dir /受控证据根/wave0
# 3. 每日证据采集结束后冻结。当日清单已存在且内容变化时拒绝覆盖。
deploy/dogfood-ops/freeze-evidence.sh \
--wave-dir /受控证据根/wave0 \
--date 2026-07-22
# 4. 收口门除全部开波条件外,还会校验真实运行时长和全部实际 UTC 运行日;
# 通过后由工具生成 close-result.json。
deploy/dogfood-ops/check-close.sh --wave-dir /受控证据根/wave0
# 5. 只有 Wave0 收口回执仍可复核时,才允许初始化 Wave1。
deploy/dogfood-ops/init-wave.sh \
--root /受控证据根 \
--wave wave1 \
--rc-commit <Wave1使用的commit-pinned-RC的40位哈希>
GATE_PASS 只表示本地证据在当前规则下可对账,不替代人工判断,不代表 staging 已部署、告警已送达或波次已完成。真实结论必须回到原始截图、网络记录、日志和数据库 ID 核验。
3. 证据清单
| 文件 | 必填内容 |
|---|---|
manifest.json |
固定波次、人数、持续时间、commit-pinned rcCommit 和工具生成的 startedAt;Wave1 另绑定 Wave0 收口回执、最终冻结哈希和 closedAt |
rc.json |
完整 40 位 commit、tracked 干净标志、rcArtifactRef 与实际 rcArtifactSha256,只证明部署候选 |
config-identity.json |
同一 RC commit、配置包路径、实际 SHA-256、采集时间 |
snapshot.json |
MySQL 快照路径/哈希,以及非 staging/production 目标的隔离恢复 PASS、时间和报告 |
storage-audit.json |
对象存储审计报告、实际哈希、采集时间 |
accounts.json |
/2:绑定激活 run-id、签名数据库报告、报告哈希/采集时间,以及本波明确账号 ID+用户名 |
role-permissions.json |
创作者本账号允许、跨账号拒绝、审核员只审、审核员改配置拒绝、只读写拒绝;每项带截图 |
main-chain.json |
同一 project/version/game 的 gameArtifactSha256,分别绑定 APPROVED 审核记录、实际 runtime package 和主链阶段真实使用的数据库证据;完整覆盖 create→generate→preview→submit-review→approve→feed→play→telemetry |
release-negatives.json |
DRAFT、REVIEWING、REJECTED、直调绕过、批准后产物漂移、下架后 feed/详情/深链八类拒绝证据 |
manual-play.json |
每个发布游戏在进入 feed 前由 /gstack@mini-desktop 真玩,带测试人、时间和截图 |
performance.json |
固定环境下至少 30 次独立 cold 导航;每条绑定 /gstack 原始 CDP trace/HAR 路径、实际 SHA-256、采集时间与采集器身份 |
p0.tsv |
单一 P0 账本;任一 severity=P0 行都会拒绝开波和收口 |
telemetry.json |
期望完整事件数、完整收到数、原始证据;完整率至少 95% |
alerts.json |
至少一条接收时间、接收人、外部消息 id 和原始证据,不接受仅写 PASS |
oncall.tsv |
无空档覆盖本波次完整时长的主备值班与联系方式 |
feedback.tsv |
唯一反馈账本;每行 id、时间、报告人、角色、分类、级别、摘要、状态、owner、证据都完整 |
manual-acceptance.tsv |
角色权限、主链、发布负例、入 feed 前真玩四项人工签认 |
drills.json |
stop/delete/rollback 三类演练的精确 ACK、操作人、时间和目录内证据 |
safety-status.json |
固定停止条件逐项评估,任一缺失或触发即拒绝 |
freezes/*.sha256.json |
工具生成的 createdAt、当日全部证据和附件 SHA-256;覆盖 startedAt 到收口时的每个实际 UTC 日期,并以前序清单哈希串联 |
close-result.json |
仅在收口门通过后生成;绑定 manifest、最终冻结清单、真实 startedAt/closedAt 和三类 Wave1 阻断计数 |
所有时间使用带时区 ISO 8601,例如 2026-07-22T08:00:00Z。哈希必须是小写 64 位 SHA-256,并与附件内容一致。
3.1 账号报告 /2
账号报告不能手写。采集器必须在 mini-infra 以 root signer 身份运行。它先固定通过 root@100.64.0.7 读取 /root/game-staging/releases/active 的 canonical commit、该 release 的单行 RC_STATUS,以及 RC_STATUS 指向的 activation evidence;只有 activation.log 最后两个非空记录分别证明同一 commit 的 RC_ACTIVATION_PASS 和同一路径的 ACTIVATION_EVIDENCE 才继续。命令行 --rc-commit 与 --activation-run-id 只是 expected 值,不能成为报告事实来源;任一参数与运行态不一致均拒签。采集器在数据库查询后再次读取相同状态,active 指针、RC_STATUS 或 evidence 发生漂移也拒签。
运行态身份稳定后,采集器在 MySQL READ ONLY 事务内取得 40 条 CLAIMED 和 12 条 FREE 池记录;再固定查询 mini-infra 本机 infra-postgres/new-api,从容器已有的 POSTGRES_PASSWORD 设置 PGPASSWORD,不把密码放入参数、日志或报告。每条池记录必须按 newapiUserId + newapiTokenId 唯一命中 canonical neice_NNN 用户/token,且 token 同时满足 status=1、unlimited_quota=false、expired_time=-1、remain_quota>0。任一禁用、耗尽、ID 漂移、重复行或多 token 匹配都会在签名前失败。
# 在可信工作站从指定 RC commit 导出脚本,不使用当前未提交工作树。
set -euo pipefail
umask 077
: "${RC_COMMIT:?请先设置 commit-pinned RC 的 40 位哈希}"
: "${ACTIVATION_RUN_ID:?请先设置 active release 的 activation run-id,例如 20260723T010203Z}"
[[ "$RC_COMMIT" =~ ^[0-9a-f]{40}$ ]]
[[ "$ACTIVATION_RUN_ID" =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$ ]]
PINNED_SCRIPT="$(mktemp)"
git show "${RC_COMMIT}:deploy/collect-dogfood-account-report.sh" >"$PINNED_SCRIPT"
chmod 700 "$PINNED_SCRIPT"
LOCAL_SHA="$(shasum -a 256 "$PINNED_SCRIPT" | awk '{print $1}')"
# mini-infra 不要求存在项目仓库;先传入临时名,哈希一致后再原子安装到固定 signer 目录。
REMOTE_INCOMING="/root/dogfood-r1-signer/collect-dogfood-account-report.sh.incoming-$RC_COMMIT"
scp -o BatchMode=yes -o StrictHostKeyChecking=yes \
"$PINNED_SCRIPT" "root@100.64.0.8:$REMOTE_INCOMING"
REMOTE_SHA="$(ssh -o BatchMode=yes -o StrictHostKeyChecking=yes root@100.64.0.8 \
"sha256sum '$REMOTE_INCOMING' | cut -d ' ' -f 1")"
[ "$LOCAL_SHA" = "$REMOTE_SHA" ]
ssh -o BatchMode=yes -o StrictHostKeyChecking=yes root@100.64.0.8 \
"install -o root -g root -m 700 '$REMOTE_INCOMING' /root/dogfood-r1-signer/collect-dogfood-account-report.sh.new && \
mv -f /root/dogfood-r1-signer/collect-dogfood-account-report.sh.new /root/dogfood-r1-signer/collect-dogfood-account-report.sh && \
rm -f '$REMOTE_INCOMING'"
rm -f "$PINNED_SCRIPT"
# 在 mini-infra 运行固定安装件;报告和签名均留在 signer 主机的 0700 目录。
ssh -o BatchMode=yes -o StrictHostKeyChecking=yes root@100.64.0.8 \
"install -d -o root -g root -m 700 /root/dogfood-account-reports && \
/root/dogfood-r1-signer/collect-dogfood-account-report.sh \
--activation-run-id '$ACTIVATION_RUN_ID' \
--rc-commit '$RC_COMMIT' \
--output /root/dogfood-account-reports/wave0-account-report.json"
采集器使用固定私钥 /root/dogfood-r1-signer/r1-ed25519-private.pem,生成报告及同名 .sig,两者权限均为 0600。报告包含账号身份、池绑定、newapiUsername、tokenStatus、tokenRemainQuota、tokenUnlimitedQuota 和 tokenExpiredTime,不输出 token key、手机号、密码或 OAuth token。通过严格主机密钥校验把两个文件取回可信工作站,再以 0600 安装进波次附件:
TRANSFER_DIR="$(mktemp -d)"
chmod 700 "$TRANSFER_DIR"
scp -o BatchMode=yes -o StrictHostKeyChecking=yes \
root@100.64.0.8:/root/dogfood-account-reports/wave0-account-report.json \
root@100.64.0.8:/root/dogfood-account-reports/wave0-account-report.json.sig \
"$TRANSFER_DIR/"
install -m 600 "$TRANSFER_DIR/wave0-account-report.json" /受控证据根/wave0/attachments/
install -m 600 "$TRANSFER_DIR/wave0-account-report.json.sig" /受控证据根/wave0/attachments/
rm -rf "$TRANSFER_DIR"
报告中的 rcCommit、activationRunId、activationCompletedAt、activeReleaseRef、activationEvidenceRef 和 activationStatus=PASS 全部来自上述两次一致的 mini-desktop 观测,不复制命令参数。随后填写:
{
"schema": "dogfood-accounts/2",
"activationRunId": "20260723T010203Z",
"databaseReportRef": "attachments/wave0-account-report.json",
"databaseReportSignatureRef": "attachments/wave0-account-report.json.sig",
"databaseReportSha256": "<报告文件SHA-256>",
"databaseReportCapturedAt": "<报告内capturedAt原值>",
"selectedAccounts": [
{"id": 1001, "username": "dogfoodc01"},
{"id": 1101, "username": "dogfoodp01"}
]
}
selectedAccounts 必须列出本波全部成员:Wave0 精确 3 名创作者和 5 名玩家,Wave1 精确 10 名创作者和 30 名玩家。证据门只使用仓内 deploy/trust/r1-attestation-ed25519.pub 验签,不读取 accounts.json、环境变量或命令行中的公钥路径。缺签、签名篡改、错误 issuer、固定查询身份漂移、RC 与 manifest 不一致、new-api user/token 身份重复、token 禁用/耗尽/unlimited/非永久有效都会失败关闭。
4. 首屏 P75 口径
采样环境固定为 mini-desktop、Chrome 146、390x844@1、Tailscale staging。每次禁用缓存后独立导航,从 navigation commit 计到首个可交互游戏帧;每条记录使用唯一 navigationId 和唯一原始文件,并标记 cache=cold、source=/gstack@mini-desktop。collector 必须包含 tool=/gstack、host=mini-desktop 和非空 sessionId,且与原始文件内身份一致;collectedAt 必须带时区、不早于波次开始且不在未来。热缓存另行报告,禁止写入 performance.json。
门不接受样本自填 durationMs。CDP trace 必须各有一个同 navigationId 的 dogfood.navigationCommit 和 dogfood.firstInteractiveGameFrame 微秒事件;HAR 必须在对应 page 的 _gstack 中记录这两个原始时间点。门从受 SHA-256 保护的原始文件重算每条时长,再按 nearest-rank 排序,排名为 ceil(0.75 × n)。30 个样本取第 23 个值;门槛严格为 P75 < 3000ms,2999ms 通过,3000ms 失败,少于 30 个样本直接失败。
5. 停止条件
出现以下任一情况,立即停止扩容;前六项同时要求全局停止:
- 安全事件、跨账号访问、数据丢失、真实扣款、真实广告曝光、未审内容进入 feed。
- 生成成功率低于 80%。
- 出现任一确认假阳。
- 同一阻断根因影响两人。
- 埋点缺失超过 5%。
- 待审超过 4 小时。
- 验收成本突破生成 v3 上限。
safety-status.json 必须逐项使用固定 condition 名称评估。不要删除已触发项来让闸门变绿。
6. 回滚顺序
真实操作只由批准的部署窗口人工执行。本目录不提供环境命令,固定顺序不可调整:
- 后端关闭新账号与提交审核。
- 后端 feed 总闸切为 fail-closed。
- 生成验收从 v3 退回
v3_shadow。 - 下架受影响游戏。
- 回退到上一 commit-pinned RC。
- 保留全部证据,创建或关联根因工单。
首个 RC 没有上一版 commit-pinned RC 时,第 5 步不可伪造为已演练;保持 rollback 红灯,等第二个 RC 窗口补证。
7. 停服、删档和回滚演练记录
先由获批人员在隔离目标完成真实演练并保存证据,再用本工具签收。精确 ACK 只证明操作者明确选择了对应波次和动作,不会触发环境操作。
deploy/dogfood-ops/record-drill.sh \
--wave-dir /受控证据根/wave0 \
--action stop \
--ack ACK-DOGFOOD-wave0-stop \
--operator 值班人ID \
--performed-at 2026-07-22T08:00:00Z \
--evidence-ref attachments/stop-drill-report.json
delete 和 rollback 分别使用 ACK-DOGFOOD-wave0-delete、ACK-DOGFOOD-wave0-rollback。同一动作、同一记录重复执行幂等返回;已有不同记录时拒绝覆盖。
删档范围只能是当前 batchId 关联的账号、项目、版本、feed 和遥测业务数据。审核记录、安全日志、发布证据、根因工单至少保留 180 天,不得随业务删档删除。删档报告必须列出实际删除对象和保留对象,不能只写总数。
8. 每日冻结
每天完成采集后运行 freeze-evidence.sh。--date 必须等于工具当前 UTC 日期,不能填未来日期,也不能回填历史日期;清单中的 createdAt 由工具同时生成。清单覆盖该波次目录下除 freezes/ 和工具生成的 close-result.json 外的全部普通文件,并记录上一日清单哈希。工具拒绝符号链接、日期空档和同日覆盖;收口时按 startedAt 到当前时间的全部 UTC 日期逐日对账,因此跨日运行的 Wave0 通常需要两份,运行 72 小时的 Wave1 通常需要四份,不能在同一分钟伪造三天。若当天冻结后又补证,当日清单会拒绝覆盖,波次保持红灯。
9. 真实采集待办
本地测试 fixture 不可用于开波。真实执行仍需从批准窗口采集:
- commit-pinned RC、制品和配置快照的原始文件及哈希;
- MySQL 本轮表快照、无网络隔离恢复报告、对象存储前缀审计;
- wave0/wave1 精确账号与 FREE 额度盘点;
/gstack在 mini-desktop 产生的角色权限、完整主链、八类发布负例、逐游戏入 feed 前真玩截图与网络证据;- 30 次独立冷导航的
/gstack原始 CDP trace/HAR、逐文件哈希、采集时间和采集器身份,以及单列的热缓存报告; - P0、埋点完整率、告警接收回执、值班覆盖和反馈账本;
- 停服、限定 batchId 删档、第二个 RC 回滚的人工演练证据;
- 每个采集日的 SHA-256 冻结清单。
10. 本地验证
python3 -m unittest discover -s deploy/dogfood-ops/tests -v
find deploy/dogfood-ops -name '*.sh' -exec bash -n {} +
python3 -m py_compile deploy/dogfood-ops/dogfood_ops.py deploy/dogfood-ops/tests/test_dogfood_ops.py
git diff --check -- deploy/dogfood-ops