lili bcb7fec906
Some checks failed
contract-gates / contract-gates (push) Has been cancelled
docs-gate / docs-gate (push) Has been cancelled
fix(release): bind dogfood gates to runtime state
2026-07-23 02:49:08 -07:00

233 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 内部狗粮运行保障
本目录把 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。
```bash
deploy/dogfood-ops/init-wave.sh \
--root /受控证据根 \
--wave wave0 \
--rc-commit <commit-pinned-RC的40位哈希>
```
生成目录中的 JSON 带 `_template: true`,表示它还不是证据。填写完成后删除该字段。附件统一放在波次目录内的 `attachments/`;JSON/TSV 只写相对路径。绝对路径、`..` 路径、符号链接和不存在的附件都会被拒绝。
## 2. 执行顺序
开波前按以下顺序执行,任一步非零都保持发布冻结:
```bash
# 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 匹配都会在签名前失败。
```bash
# 在可信工作站从指定 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` 安装进波次附件:
```bash
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 观测,不复制命令参数。随后填写:
```json
{
"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. 停止条件
出现以下任一情况,立即停止扩容;前六项同时要求全局停止:
1. 安全事件、跨账号访问、数据丢失、真实扣款、真实广告曝光、未审内容进入 feed。
2. 生成成功率低于 80%。
3. 出现任一确认假阳。
4. 同一阻断根因影响两人。
5. 埋点缺失超过 5%。
6. 待审超过 4 小时。
7. 验收成本突破生成 v3 上限。
`safety-status.json` 必须逐项使用固定 condition 名称评估。不要删除已触发项来让闸门变绿。
## 6. 回滚顺序
真实操作只由批准的部署窗口人工执行。本目录不提供环境命令,固定顺序不可调整:
1. 后端关闭新账号与提交审核。
2. 后端 feed 总闸切为 fail-closed。
3. 生成验收从 v3 退回 `v3_shadow`。
4. 下架受影响游戏。
5. 回退到上一 commit-pinned RC。
6. 保留全部证据,创建或关联根因工单。
首个 RC 没有上一版 commit-pinned RC 时,第 5 步不可伪造为已演练;保持 rollback 红灯,等第二个 RC 窗口补证。
## 7. 停服、删档和回滚演练记录
先由获批人员在隔离目标完成真实演练并保存证据,再用本工具签收。精确 ACK 只证明操作者明确选择了对应波次和动作,不会触发环境操作。
```bash
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. 本地验证
```bash
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
```