Files
ai-contract-platform/project-docs/V2-public-policy-production-promotion-runbook-2026-08-17.md
yuqei 801a9b04bc
Build and Test Quality Gate / Contract Web typecheck and build (pull_request) Successful in 4m4s
Build and Test Quality Gate / Python tests and Contract API migration (pull_request) Failing after 9m18s
Security Quality Gate / npm audit, Bandit, pip-audit, detect-secrets (pull_request) Successful in 8m20s
feat: add governed public policy promotion
2026-08-17 19:52:07 +08:00

186 lines
7.4 KiB
Markdown
Raw Permalink 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.
# V2 公开政策生产 Promotion 运维手册
日期:2026-08-17
## 边界
本手册只描述已验签公开政策的受控状态切换。它不替代法务审批、运营审批、容量审批或公网切流审批。候选 keyring、手工修改数据库、HTTP 写接口、政策私钥和共享预发布凭据都不能用于生产激活。
`CONTRACT_API_PUBLIC_POLICY_EXECUTOR_ENABLED` 必须保持 `false`。生产读取、注册协议和既有用户重新同意由 Contract API 提供;真正的 promotion 和 rollback 只通过本机受控 CLI 执行,并且不暴露 HTTP 路由。
## 前置条件
1. Contract API 数据库已经迁移到 `20260812_0046`,`public_policy_state` 存在且状态指针可验证。
2. 四份政策候选文件、`manifest.json`、具名法务审批文件和 trusted keyring 已由安全/运维安装到生产主机。
3. 材料目录和文件均为绝对路径,文件所有者可读,未包含 symlink,目录总量不超过 2 MiB。
4. `CONTRACT_API_PUBLIC_POLICY_RELEASE_DIGEST` 等于当前 Contract API 不可变镜像 digest。
5. 生产只读 preflight 通过。
6. 已取得单独的运营激活授权文件。该文件只用于计算 SHA-256,内容不得进入仓库、聊天、日志或 CI artifact。
7. 已记录 expected generation、current set ID、trace ID、idempotency key 和授权文件 hash。
推荐主机布局:
```text
/etc/yuqei/public-policy/2026-08-12/
manifest.json
user-terms.md
privacy-policy.md
service-agreement.md
ai-use-notice.md
named-policy-legal-approval.json
policy-legal-keyring.trusted.json
```
生产环境模板中的绑定:
```text
PUBLIC_POLICY_CANDIDATE_DIR_HOST=/etc/yuqei/public-policy/2026-08-12
PUBLIC_POLICY_LEGAL_APPROVAL_FILE_HOST=/etc/yuqei/public-policy/2026-08-12/named-policy-legal-approval.json
PUBLIC_POLICY_TRUSTED_KEYRING_FILE_HOST=/etc/yuqei/public-policy/2026-08-12/policy-legal-keyring.trusted.json
CONTRACT_API_PUBLIC_POLICY_RELEASE_DIGEST=sha256:<contract-api-image-digest>
CONTRACT_API_PUBLIC_POLICY_EXECUTOR_ENABLED=false
```
## 1. 只读预检
在生产主机执行:
```bash
python3 yuqei-infra/deploy/preflight-production.py \
--env-file /etc/yuqei/production.env \
--artifact-dir /var/lib/yuqei/preflight/public-policy
```
必须确认以下检查为 `pass`:
- `public_policy_deployment`
- `public_policy_credential_isolation`
- `public_policy_candidate_dir`
- `public_policy_manifest`
- `public_policy_legal_approval_file`
- `public_policy_trusted_keyring_file`
- 相关 owner-only 权限检查
任一失败时停止,不得通过改名、复制、手工改库或跳过 preflight 继续。
## 2. 查看当前状态
下面的 `COMPOSE_ARGS` 按生产部署手册替换为真实 project、env 和两个 Compose 文件参数:
```bash
COMPOSE_ARGS=(
--project-name yuqei-v2-production
--env-file /etc/yuqei/production.env
-f yuqei-infra/compose/docker-compose.yml
-f yuqei-infra/compose/docker-compose.production.yml
)
docker compose "${COMPOSE_ARGS[@]}" run --rm --no-deps contract-api \
python -m yuqei_contract_api.public_policy_promotion_cli status
```
输出不包含政策正文,只包含状态指针、版本、生效时间、公开路径和每个文件的 SHA-256。
## 3. Promotion dry-run
```bash
docker compose "${COMPOSE_ARGS[@]}" run --rm --no-deps contract-api \
python -m yuqei_contract_api.public_policy_promotion_cli preflight
```
显式 promotion dry-run 还必须提供工单上的 expected state:
```bash
docker compose "${COMPOSE_ARGS[@]}" run --rm --no-deps contract-api \
python -m yuqei_contract_api.public_policy_promotion_cli \
promote \
--expected-generation 0 \
--expected-current-set-id none \
--idempotency-key policy-promotion-20260812-001 \
--trace-id ops-policy-promotion-20260812-001
```
Dry-run 不写数据库,也不需要授权文件。
## 4. 受控 Promotion
授权文件通过一次性只读挂载进入容器。不要把该挂载写入生产 Compose 文件:
```bash
AUTHORIZATION_FILE=/run/yuqei/operations/policy-promotion-20260812-001.txt
docker compose "${COMPOSE_ARGS[@]}" run --rm --no-deps \
--volume "$AUTHORIZATION_FILE:/run/yuqei/public-policy/activation-authorization.txt:ro" \
contract-api \
python -m yuqei_contract_api.public_policy_promotion_cli \
promote \
--apply \
--confirmation I_AUTHORIZE_THIS_EXACT_PUBLIC_POLICY_PROMOTION \
--authorization-file /run/yuqei/public-policy/activation-authorization.txt \
--expected-generation 0 \
--expected-current-set-id none \
--idempotency-key policy-promotion-20260812-001 \
--trace-id ops-policy-promotion-20260812-001
```
命令输出中的 `authorization_sha256`、generation、set ID、event ID 和 state hash 必须写入运维证据。重复执行同一命令必须使用完全相同的材料;换任何材料必须换 idempotency key 并重新审批。
## 5. 生效后回归
到达批准的 `effective_at` 后验证:
1. `GET /api/v1/public-policies/current` 返回四份政策。
2. 四份政策页面返回 approved 字节,页面版本和生效时间正确。
3. 新注册用户必须绑定四个 effective 协议版本。
4. 既有用户下次登录进入重新同意流程。
5. 同意提交记录版本、时间和主体。
6. 审计和 append-only 政策事件链完整。
7. Contract Web 不出现未发布、接口故障与真实空态混淆。
8. 生产 health、schema-check、worker、Provider readiness 和核心合同流程保持通过。
## 6. 受控 Rollback
Rollback 只允许指向曾经 active 的历史 policy set,不能伪造或引用不存在的 set。先执行 dry-run:
```bash
docker compose "${COMPOSE_ARGS[@]}" run --rm --no-deps contract-api \
python -m yuqei_contract_api.public_policy_promotion_cli \
rollback \
--target-set-id <historical-policy-set-id> \
--expected-generation <ticket-generation> \
--expected-current-set-id <ticket-current-set-id> \
--idempotency-key policy-rollback-<ticket-id> \
--trace-id ops-policy-rollback-<ticket-id>
```
确认目标后,使用单独的 rollback 授权文件:
```bash
ROLLBACK_AUTHORIZATION=/run/yuqei/operations/policy-rollback-<ticket-id>.txt
docker compose "${COMPOSE_ARGS[@]}" run --rm --no-deps \
--volume "$ROLLBACK_AUTHORIZATION:/run/yuqei/public-policy/rollback-authorization.txt:ro" \
contract-api \
python -m yuqei_contract_api.public_policy_promotion_cli \
rollback \
--apply \
--confirmation I_AUTHORIZE_THIS_EXACT_PUBLIC_POLICY_ROLLBACK \
--authorization-file /run/yuqei/public-policy/rollback-authorization.txt \
--target-set-id <historical-policy-set-id> \
--expected-generation <ticket-generation> \
--expected-current-set-id <ticket-current-set-id> \
--idempotency-key policy-rollback-<ticket-id> \
--trace-id ops-policy-rollback-<ticket-id>
```
Rollback 也是 append-only 事件,不删除历史政策、同意记录或审计。执行后必须重复第 5 节回归,并通知法务和运营具名复核。
## 7. 明确禁止
- 禁止把法务私钥、运营授权文件或任何 policy signing key 放入仓库、生产环境文件或 CI artifact。
- 禁止使用 candidate keyring 直接激活。
- 禁止把 trusted keyring 的 `policy_activation_authorized=false` 手工改为 true。
- 禁止直接 UPDATE/DELETE `public_policy_*` 表。
- 禁止新增 HTTP promotion、rollback 或 execute 路由。
- 禁止在 preflight、容量、备份、法务或运营审批失败时启动 edge 或切换 DNS。
- 禁止把 dry-run 或技术验签输出宣称为法务批准或公网切流授权。