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
186 lines
7.4 KiB
Markdown
186 lines
7.4 KiB
Markdown
# 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 或技术验签输出宣称为法务批准或公网切流授权。 |