233 lines
16 KiB
Markdown
233 lines
16 KiB
Markdown
# 内部狗粮运行保障
|
||
|
||
本目录把 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
|
||
```
|