docs(sub2api): 明确补偿公告适用范围

This commit is contained in:
Codex
2026-07-15 16:11:41 +02:00
parent a532dbd751
commit b77a22af6f
4 changed files with 52 additions and 1 deletions
@@ -164,6 +164,12 @@ bun scripts/cli.ts platform-infra sub2api codex-pool cleanup-probes --target D60
- `announcements list|get|create` 管理 Sub2API 原生公告:
- `list``get` 只读调用原生 `/api/v1/admin/announcements`,支持分页、状态、搜索、排序和按 ID 下钻;
- `create` 支持标题、Markdown 内容、`draft|active|archived``silent|popup`、原生 `any_of/all_of` targeting 以及可选 Unix 秒或 RFC3339 开始/结束时间;targeting 必须通过 `--targeting-json` 的 JSON 解析器输入,不拼接临时结构;
- 每次 create 的用户可见正文都必须明确业务时间范围和接收范围;`targeting` 只控制投递对象,不能替代公告正文中的业务适用范围;
- 补偿公告必须在正文逐项列出:
- 故障影响时间窗或补偿资格统计时间窗;
- 全部实际接收用户的稳定用户 ID 和邮箱;邮箱可以脱敏,但禁止只写“符合条件的用户”或其他无法对账的泛化描述;
- 每名用户的补偿金额和本批总额;候选名单与实际发放名单不一致时,两者及排除原因都要明确;
- create dry-run 和 confirm 输出都显示人工范围检查;CLI 不根据标题关键词猜测公告类型,也不把内容出现某些词误判为范围已经完整,操作者必须在 confirm 前核对正文;
- `create` 默认只输出完整预览,固定 `mutation=false``writeAttempted=0`,不登录运行面也不调用创建 API
- 只有显式 `--confirm` 才调用原生创建 API,并按返回的正整数 ID 回读;对账必须逐字段核对 `title``content``status``notify_mode``targeting``starts_at``ends_at`,输出 `mismatchedFields`,任一字段不一致都不得报告成功;发布公告属于面向客户的外部影响操作,必须取得用户对本次具体标题、内容、状态、通知方式、targeting 和时间窗的明确授权;
- 默认输出 Kubernetes 风格紧凑文本,显式 `--json` 输出同一份机器结构;命令不打印管理凭据或 Secret。
@@ -0,0 +1,37 @@
# R3.7 公告范围规则固化报告
## 目标
补偿公告不能只写“符合条件的用户”。公告必须让操作者和接收方明确知道补偿对应哪个时间段、哪些用户以及多少金额;本次只固化规则,不重新发布或修改既有公告。
## Skill 规则
Sub2API `codex-pool.md` 已明确:
- 每次 create 的用户可见正文必须写明业务时间范围和接收范围。
- `targeting` 只控制公告投递,不能替代补偿资格或实际发放范围。
- 补偿公告必须列出故障影响时间窗或补偿资格统计时间窗。
- 必须列出全部实际接收用户的稳定用户 ID 和邮箱;邮箱可以脱敏,但禁止只写泛化描述。
- 必须列出每人金额和本批总额。
- 候选名单与实际发放名单不一致时,必须同时写明两者和排除原因。
## CLI 可见性
公告 create 的 dry-run 和 confirm 输出新增两条固定人工检查:
- `SCOPE-CHECK`:正文必须明确时间范围和接收范围,targeting 不能替代业务范围。
- `COMPENSATION-CHECK`:列出全部接收用户 ID、可脱敏邮箱、每人金额和总额。
CLI 不通过标题关键词猜测公告类型,也不做容易误判的内容关键词自动通过;最终 confirm 前由操作者核对完整正文。
## 验证
- 使用包含参数化时间窗、用户 ID、脱敏邮箱、每人金额和总额的公告执行 dry-run。
- 输出 `mutation=false``writeAttempted=0`,两条范围检查和完整正文均可见。
- 未登录运行面,未调用公告创建 API,未修改或重新发布公告 ID 1。
- Sub2API skill `quick_validate` 通过。
- `git diff --check` 通过。
## 结论
后续补偿公告在发布前会同时经过 skill 规则和 CLI create 预览提醒,范围必须可由时间窗和用户 ID 对账;上次公告仅保留历史事实,不做重发。
@@ -243,3 +243,7 @@
### R3.6 [completed]
按用户明确授权,对固定补偿候选用户 ID 11、10、7、22、19 每人增加 30 USD,总额 150 USD,并并发发布服务稳定性补偿公告;两条操作都必须通过 Sub2API 原生受控 CLI 显式 confirm,使用精确名单、写后回读和公告字段对账,禁止扩大到 YAML 排除员工或管理员,完成任务后将详细报告写入[任务报告](./details/sub2api-upstream-reliability/R3.6_Task_Report.md)。
### R3.7 [completed]
在 Sub2API 公告 skill 与 CLI create 预览中固化明确范围规则:补偿公告必须在用户可见正文列出故障或补偿时间窗、全部实际接收用户、每人金额和总额;邮箱允许脱敏但必须保留稳定用户 ID,`targeting` 仅表示投递范围、不能替代补偿资格名单。本次只记录并验证 dry-run 展示,不重新发布或修改既有公告,完成任务后将详细报告写入[任务报告](./details/sub2api-upstream-reliability/R3.7_Task_Report.md)。
@@ -169,6 +169,8 @@ function renderAnnouncements(response: Record<string, unknown>): RenderedCliResu
const announcement = record(report.announcement);
const mismatchedFields = Array.isArray(report.mismatchedFields) ? report.mismatchedFields.join(",") : "-";
lines.push(`WRITE attempted=${text(report.writeAttempted)} reconciled=${text(report.reconciled)} id=${text(announcement.id)} mismatched-fields=${mismatchedFields || "-"}`);
lines.push("SCOPE-CHECK manual-required: 正文必须明确时间范围和接收范围;targeting 不能替代业务范围说明");
lines.push("COMPENSATION-CHECK manual-required: 列出全部接收用户 ID、可脱敏邮箱、每人金额和总额");
lines.push(`TITLE ${text(announcement.title)}`, `STATUS ${text(announcement.status)} NOTIFY_MODE ${text(announcement.notify_mode)}`, `STARTS_AT ${text(announcement.starts_at)} ENDS_AT ${text(announcement.ends_at)}`, `TARGETING ${JSON.stringify(record(announcement.targeting))}`, "CONTENT", String(announcement.content ?? ""));
}
if (typeof report.error === "string") lines.push(`ERROR ${text(report.error)}`);
@@ -184,7 +186,9 @@ function renderAnnouncementsHelp(): RenderedCliResult {
"platform-infra sub2api codex-pool announcements list [--page 1] [--page-size 20] [--status draft|active|archived] [--search text] [--sort-by created_at] [--sort-order asc|desc] [--target PK01] [--json]",
"platform-infra sub2api codex-pool announcements get --id <positive-integer> [--target PK01] [--json]",
"platform-infra sub2api codex-pool announcements create --title <text> (--content <markdown>|--content-file <path>) [--announcement-status draft|active|archived] [--notify-mode silent|popup] [--targeting-json <json-object>] [--starts-at <unix-seconds|RFC3339>] [--ends-at <unix-seconds|RFC3339>] [--target PK01] [--confirm] [--json]",
"create defaults to dry-run; only --confirm calls the native Sub2API create API and then reconciles every planned announcement field after reading the returned ID back.",
"create 默认 dry-run;只有 --confirm 才调用 Sub2API 原生创建 API,并按返回 ID 回读对账全部公告字段。",
"范围要求:正文必须明确时间范围和接收范围;--targeting-json 只控制投递,不能替代业务适用范围。",
"补偿公告:必须列出故障或补偿时间窗、全部实际接收用户 ID、可脱敏邮箱、每人金额和总额;禁止只写‘符合条件的用户’。",
].join("\n");
return { ok: true, command: "platform-infra sub2api codex-pool announcements --help", renderedText, contentType: "text/plain", projection: { ok: true, mutation: false } };
}