补充安全权限日志与草稿状态约定

This commit is contained in:
talesofzes
2026-06-22 17:30:59 +08:00
parent df0b3fa267
commit a41e2c28d4
19 changed files with 451 additions and 129 deletions
+10 -3
View File
@@ -66,24 +66,30 @@
1. `docs/contracts/状态流转约定.md`
涉及权限、安全、日志脱敏、飞书验签、幂等或敏感信息时再读:
1. `docs/contracts/安全权限日志约定.md`
涉及飞书时再读:
1. `docs/specs/03_飞书通知与反馈.md`
2. `docs/contracts/API接口约定.md`
3. `docs/contracts/安全权限日志约定.md`
涉及 Review 或提交前检查时再读:
1. `docs/checklists/AI生成代码检查清单.md`
2. `docs/checklists/后端Review清单.md`
3. 本次新增或修改的 tests
不要每次全量读取所有 docs,避免上下文过大、重点变散。
## 事实源优先级
1. `docs/contracts/` 是字段、接口、状态、错误码和全局约定的唯一事实源。
1. `docs/contracts/` 是字段、接口、状态、错误码、安全权限日志和全局约定的唯一事实源。
2. `docs/specs/` 说明业务边界和模块目标,不复制完整字段表。
3. `docs/plans/active/` 是当前施工依据,不用于偷偷改变业务边界。
4. `docs/checklists/`测试是 Review 闸门。
4. `docs/checklists/` tests 是 Review 闸门tests 是真实约束
如果 spec 和 contract 冲突,以 contract 为准,并提出文档修正建议。
@@ -99,7 +105,7 @@ PostgreSQL 中的 AI 记忆表只能辅助 AI 理解、追问、恢复会话和
2. 不允许普通员工给别人创建事项或提醒。
3. 不允许绕过后端权限校验,只靠前端隐藏按钮。
4. 不允许自动操作交易系统、公司历史数据库、后台管理系统或其他外部业务系统。
5. 不允许把 API Key、App Secret、飞书 token、完整手机号、完整邮箱写进代码或普通日志。
5. 不允许把 API Key、App Secret、飞书 token、OAuth code/state、回调验签密钥、一次性操作 token、完整手机号、完整邮箱写进代码或普通日志。
6. 不允许把老板解释中的游戏化表达写进事项标题、接收人、时间、反馈要求等结构化字段。
7. 不允许把第一版明确不做的功能混入主线。
8. 不允许只写 AI 记忆表就认为任务、提醒或通知已经创建成功。
@@ -116,6 +122,7 @@ PostgreSQL 中的 AI 记忆表只能辅助 AI 理解、追问、恢复会话和
-> 没有 plan:读必要 contracts,生成 plan 给人确认
-> 已有 plan:只按当前 plan 执行
-> AI 读 checklists
-> AI 确认本次要新增或修改哪些 tests
-> AI 按 plan 写代码
-> AI 自测
-> 泽源跑测试
+2 -1
View File
@@ -1,6 +1,6 @@
# ARCHITECTURE.md
本文是后端架构入口,说明 Django 项目内部模块边界和调用规则。具体业务需求看 `docs/specs/`,字段、接口状态以 `docs/contracts/` 为准。
本文是后端架构入口,说明 Django 项目内部模块边界和调用规则。具体业务需求看 `docs/specs/`,字段、接口状态、安全权限和日志边界`docs/contracts/` 为准。
## 架构原则
@@ -10,6 +10,7 @@
4. 所有关键动作必须写操作日志或失败记录。
5. 飞书回调、AI 输出、定时触发必须幂等。
6. 权限必须在接口层和 service 层双重校验。
7. 敏感信息脱敏、飞书验签和访问边界以 `docs/contracts/安全权限日志约定.md` 为准。
## 运行时与依赖版本
+4 -1
View File
@@ -49,11 +49,13 @@
1. `docs/contracts/API接口约定.md`
2. `docs/contracts/数据对象约定.md`
3. `docs/contracts/状态流转约定.md`
4. `docs/contracts/安全权限日志约定.md`
提交或 Review 前读:
1. `docs/checklists/AI生成代码检查清单.md`
2. `docs/checklists/后端Review清单.md`
3. 本次新增或修改的 tests
## 本地启动
@@ -75,8 +77,9 @@
3. 飞书 App Secret。
4. 飞书回调验签密钥。
5. 数据库账号密码。
6. OAuth code/state 和一次性操作 token 明文。
普通日志中不得打印 API Key、App Secret、飞书 token、完整手机号或完整邮箱。
普通日志中不得打印 API Key、App Secret、飞书 token、OAuth code/state、一次性操作 token、回调验签密钥、完整手机号或完整邮箱。
## 第一版不做
+1
View File
@@ -32,6 +32,7 @@ AI 只负责整理、建议和生成草稿,不直接创建事项、不直接
3. 定时提醒:承接一次性和固定周期提醒,到点通知并记录结果。
4. 飞书集成:身份登录、老板机器人私聊、个人消息、交互卡片和回调。
5. 权限日志失败记录:角色权限、操作日志、失败记录、敏感信息边界。
6. 安全权限日志约定:飞书验签、幂等、日志脱敏、访问范围和 tests 真实约束。
## 第一版闭环
+3
View File
@@ -24,8 +24,10 @@
| 补齐 `docs/contracts/API接口约定.md` | 田宇 / 泽源 | 进行中 |
| 补齐 `docs/contracts/数据对象约定.md` | 乔大卫 / 泽源 | 进行中 |
| 补齐 `docs/contracts/状态流转约定.md` | 乔大卫 | 进行中 |
| 新增 `docs/contracts/安全权限日志约定.md` | 乔大卫 / 田宇 / 泽源 | 进行中 |
| 补齐 `docs/checklists/AI生成代码检查清单.md` | 泽源 | 进行中 |
| 补齐 `docs/checklists/后端Review清单.md` | 乔大卫 / 田宇 | 进行中 |
| 预留并维护 `tests/` 真实约束 | 泽园 / 焕然 | 进行中 |
| 补齐第一份 active plan | 泽源 / 焕然 | 进行中 |
## 当前卡点
@@ -36,6 +38,7 @@
4. 第一批试用人员姓名、常见称呼、手机号或邮箱映射需要后续维护。
5. PostgreSQL 连接、迁移和 `jsonb` 字段使用方式需要在项目骨架创建后验证。
6. 本地当前 Python 可能不是部署基线;提交前需要在 Python 3.12.13 环境跑完整测试。
7. 当前仅预留 tests 目录,Django 骨架创建后需要把权限、状态、飞书幂等、调度幂等、日志脱敏和 AI 人工确认边界落成真实测试。
## 待确认问题
+16 -2
View File
@@ -12,6 +12,7 @@ AI 生成或修改后端代码前后都要检查本清单。checklists 是 Revie
- [ ] 涉及老板 AI 秘书、Agent、BotContext 或对话记忆时已读 `docs/specs/01_老板AI秘书与AI草稿.md`
- [ ] 涉及状态流转时已读 `docs/contracts/状态流转约定.md`
- [ ] 涉及飞书时已读 `docs/specs/03_飞书通知与反馈.md`
- [ ] 涉及权限、安全、日志脱敏、飞书验签或幂等时已读 `docs/contracts/安全权限日志约定.md`
## 2. 范围控制
@@ -35,7 +36,7 @@ AI 生成或修改后端代码前后都要检查本清单。checklists 是 Revie
- [ ] 老板消息统一走 `POST /api/secretary/handle-message`
- [ ] `source + message_id` 幂等,不重复调用模型、不重复创建草稿、不重复写 `secretary_messages`
- [ ] AI 输出 JSON 已做结构校验。
- [ ] `qa/realtime_qa/note/need_more_info/unsupported` 不创建事项、提醒或通知。
- [ ] `qa/realtime_qa/note/need_more_info/unknown/unsupported` 不创建事项、提醒或通知。
- [ ] 实时问答第一版只做暂不支持提示,不做交易判断。
- [ ] `need_more_info` 最多 3 个问题、最多 3 轮追问。
- [ ] PostgreSQL AI 对话记忆只辅助理解和草稿修订,不替代任务、提醒、通知等正式业务表。
@@ -55,8 +56,9 @@ AI 生成或修改后端代码前后都要检查本清单。checklists 是 Revie
## 6. 飞书
- [ ] 飞书回调必须验签。
- [ ] 飞书事件使用 `event_id` 幂等。
- [ ] 飞书事件使用 `event_id``idempotency_key` 幂等。
- [ ] 卡片重复点击不会重复创建事项、提醒或反馈。
- [ ] 旧卡片、失效卡片、已替代草稿和已过期通知只记录事件,不写业务对象。
- [ ] 飞书通知只发送摘要和链接。
- [ ] 非老板机器人访问不会调用 AI。
- [ ] 回调失败会写 `feishu_events``failure_records`
@@ -84,6 +86,8 @@ AI 生成或修改后端代码前后都要检查本清单。checklists 是 Revie
- [ ] 普通日志不包含 API Key、App Secret、飞书 token。
- [ ] 普通日志不包含完整手机号或完整邮箱。
- [ ] 原始错误摘要不包含密钥。
- [ ] OAuth code/state、回调验签密钥、一次性操作 token 明文不进入日志或业务表。
- [ ] `model_call_logs` 原始请求/响应和 `feishu_events.raw_payload` 访问范围受限。
## 10. 测试
@@ -93,3 +97,13 @@ AI 生成或修改后端代码前后都要检查本清单。checklists 是 Revie
- [ ] 定时测试覆盖重复触发防护。
- [ ] 事务测试覆盖通知失败不留下半截数据。
- [ ] AI 测试覆盖解析失败、实时问答和人工确认边界。
- [ ] 每个 plan 完成前已说明新增或修改了哪些 tests、覆盖哪些 spec / contract 约定、如何运行。
## 11. 文档同步
- [ ] 改接口同步 `docs/contracts/API接口约定.md`
- [ ] 改字段同步 `docs/contracts/数据对象约定.md`
- [ ] 改状态同步 `docs/contracts/状态流转约定.md`
- [ ] 改权限、安全、日志脱敏、飞书验签或幂等同步 `docs/contracts/安全权限日志约定.md`
- [ ] 改飞书逻辑同步 `docs/specs/03_飞书通知与反馈.md`
- [ ] 改 AI 规则同步 `docs/specs/01_老板AI秘书与AI草稿.md`
+8 -2
View File
@@ -11,6 +11,7 @@
- [ ] 飞书身份没有直接绕过平台角色权限。
- [ ] 非老板机器人私聊不会触发 AI。
- [ ] 失败记录仅管理员 / AI 团队和必要管理角色可见。
- [ ] 权限、安全、日志脱敏、验签和幂等符合 `docs/contracts/安全权限日志约定.md`
## 2. 状态流转
@@ -31,8 +32,9 @@
## 4. 飞书
- [ ] 回调已验签。
- [ ] `event_id` 幂等。
- [ ] `event_id``idempotency_key` 幂等。
- [ ] 卡片重复点击幂等。
- [ ] 旧卡片、失效卡片、已替代草稿和已过期通知不会继续写业务对象。
- [ ] 通知内容只包含摘要和链接。
- [ ] 飞书 token、App Secret 不进入日志。
- [ ] 找不到通知、草稿、事项、提醒时有失败记录。
@@ -43,7 +45,7 @@
- [ ] 老板消息统一走 `POST /api/secretary/handle-message`
- [ ] `source + message_id` 幂等覆盖模型调用、草稿创建和 `secretary_messages`
- [ ] 模型 JSON 有 validator。
- [ ] `qa` / `realtime_qa` / `note` / `need_more_info` / `unsupported` 不创建事项或提醒。
- [ ] `qa` / `realtime_qa` / `note` / `need_more_info` / `unknown` / `unsupported` 不创建事项或提醒。
- [ ] 实时问答不做交易判断。
- [ ] PromptContext 版本被记录。
- [ ] PostgreSQL AI 对话记忆只辅助理解和草稿修订,不替代正式业务表。
@@ -66,6 +68,8 @@
- [ ] 失败记录包含类型、关联对象、原因、处理状态。
- [ ] 失败处理必须有处理结果。
- [ ] 日志包含操作人和渠道。
- [ ] OAuth code/state、回调验签密钥、一次性操作 token、完整手机号和完整邮箱没有进入普通日志。
- [ ] 模型原始请求/响应和飞书原始 payload 的访问范围受限。
## 8. 测试
@@ -73,11 +77,13 @@
- [ ] 失败路径有测试。
- [ ] 测试没有依赖真实飞书或真实百炼网络调用。
- [ ] 测试 fixture 不包含真实手机号、邮箱、token 或密钥。
- [ ] 每个 plan 完成前说明了新增或修改的 tests、覆盖的 spec / contract 约定和运行方式。
## 9. 文档同步
- [ ] 改接口同步 `docs/contracts/API接口约定.md`
- [ ] 改字段同步 `docs/contracts/数据对象约定.md`
- [ ] 改状态同步 `docs/contracts/状态流转约定.md`
- [ ] 改权限、安全、日志脱敏、飞书验签或幂等同步 `docs/contracts/安全权限日志约定.md`
- [ ] 改飞书逻辑同步 `docs/specs/03_飞书通知与反馈.md`
- [ ] 改 AI 规则同步 `docs/specs/01_老板AI秘书与AI草稿.md`
+10 -3
View File
@@ -16,17 +16,19 @@
- [ ] AI 可以把老板一句话解析为结构化草稿。
- [ ] AI 草稿可以展示原始输入、类型、内容、接收人候选、时间、反馈要求和缺失字段。
- [ ] 草稿必须人工确认后才创建事项或提醒。
- [ ] 普通聊天、普通记录、实时问答兜底不会创建草稿、事项提醒。
- [ ] 普通聊天、普通记录、实时问答兜底`note``unknown` 不会创建事项提醒或通知
- [ ] 老板可以确认、补充/重说、取消草稿。
- [ ] 补充/重说 30 分钟上下文可以生效并过期。
- [ ] 补充/重说后旧确认卡片失效,新草稿能追溯 `parent_draft_id`
- [ ] PostgreSQL 中可以查看 `secretary_messages` 和当前 `BotContext`
- [ ] 实时问答不会创建事项、提醒或通知。
## 3. 事项闭环
- [ ] 简单事项可以从草稿生成。
- [ ] 复杂事项可以转程经理确认
- [ ] 复杂事项在老板确认后创建 `pending_manager_confirm` 事项壳
- [ ] 程经理收到飞书提醒后进入平台确认。
- [ ] 程经理补齐事项壳后才通知最终接收人。
- [ ] 事项可以飞书通知接收人。
- [ ] 接收人可以反馈已收到、处理中、已完成、有问题。
- [ ] 有问题反馈必须留下原因。
@@ -41,7 +43,7 @@
- [ ] 自己提醒自己默认不需要反馈。
- [ ] 老板 / 程经理给别人设置的提醒默认需要反馈。
- [ ] 暂停、恢复、取消按状态约定生效。
- [ ] 同一提醒不会重复触发同一通知。
- [ ] 同一提醒同一触发时间同一接收人同一渠道不会重复触发同一通知。
- [ ] 演示 / 联调环境同一应用库只启动一个 scheduler 进程。
## 5. 飞书通知和回调
@@ -51,6 +53,9 @@
- [ ] 卡片按钮回调可以被平台接收。
- [ ] 回调验签失败不会执行业务动作。
- [ ] 重复点击卡片不会重复创建反馈或转换草稿。
- [ ] 没有稳定 `event_id` 的回调使用 `idempotency_key` 幂等。
- [ ] 旧卡片、失效卡片、已替代草稿和已过期通知只记录事件,不写业务对象。
- [ ] 飞书手机端平台反馈页可以提交“有问题”原因,并拒绝过期或失效链接。
- [ ] 通知成功和失败都有记录。
## 6. 失败复盘
@@ -69,5 +74,7 @@
- [ ] 飞书通知只发摘要和链接。
- [ ] 普通日志不含 API Key、App Secret、飞书 token。
- [ ] 普通日志不含完整手机号和完整邮箱。
- [ ] 普通日志不含 OAuth code/state、回调验签密钥和一次性操作 token 明文。
- [ ] 模型原始请求/响应和飞书原始 payload 只对管理员 / AI 团队开放排查。
- [ ] 不自动操作交易系统、历史数据库、后台管理系统或其他外部业务系统。
- [ ] 第一版明确不做的功能没有混入主线。
+9 -2
View File
@@ -11,6 +11,7 @@
5. 动作类接口使用 `POST /api/{resources}/{id}/{action}`
6. 飞书回调接口必须单独记录原始 payload。
7. 后端必须做权限校验,不能只靠前端隐藏入口。
8. 涉及权限、安全、日志脱敏、飞书验签和幂等时,以 `docs/contracts/安全权限日志约定.md` 为准。
## 2. 统一响应格式
@@ -66,6 +67,7 @@
| `not_found` | 404 | 对象不存在或不可见 |
| `state_conflict` | 409 | 当前状态不允许该操作 |
| `idempotency_conflict` | 409 | 幂等键冲突或重复请求 |
| `card_expired` | 409 | 飞书卡片、通知或一次性操作链接已失效 |
| `ai_parse_failed` | 422 | AI 输出无法解析或不符合结构 |
| `ai_model_failed` | 502 | 百炼或模型网关失败,重试后仍不可用 |
| `missing_person_mapping` | 422 | 缺少人员映射 |
@@ -146,6 +148,8 @@
- `unsupported`
- `error`
`note` / `unknown` / `qa` / `realtime_qa` / `need_more_info` / `unsupported` 不得创建事项、提醒或通知;如需留痕,只返回回复、追问、上下文摘要或失败信息。
兼容接口 `/api/ai-drafts/parse``/api/demo/boss-message` 可以保留,但内部必须转发到 `/api/secretary/handle-message`
## 7. 事项
@@ -200,9 +204,11 @@
1. 必须验签。
2. 必须记录原始 payload 到 `feishu_events`
3. 必须使用 `event_id` 幂等。
3. 必须使用 `event_id` 幂等;没有稳定 `event_id` 时必须使用 `idempotency_key`
4. 验签失败不得执行业务动作。
5. 回调处理失败必须写入 `failure_records`
5. 草稿确认类卡片必须校验 `ai_drafts.active_card_notification_id`
6. 旧卡片、失效卡片、已替代草稿和已过期通知的回调只记录 `feishu_events`,不写业务对象。
7. 回调处理失败必须写入 `failure_records`
## 11. 敏感信息返回边界
@@ -210,3 +216,4 @@
2. 手机号和邮箱默认脱敏返回,除非管理员维护页确有需要。
3. 飞书通知只发送摘要和链接,不发送敏感全文。
4. 模型调用日志对外接口默认不返回完整 prompt 和原始大段上下文。
5. 飞书原始 payload、模型原始请求/响应、OAuth code/state、一次性操作 token 和回调验签密钥的访问边界以 `安全权限日志约定.md` 为准。
+136
View File
@@ -0,0 +1,136 @@
# 安全权限日志约定
本文件是第一版安全、权限、日志脱敏、回调验签、幂等和访问边界的唯一事实源。涉及这些规则时,代码、接口、测试和 Review 结论应以本文件为准。
本文件不展开完整 RBAC、安全合规体系或审计平台,只卡住最小闭环里 AI 和后端最容易写错的硬规则。
## 1. 核心底线
1. AI 只能生成草稿、追问或回复,不能绕过人工确认直接创建事项、创建提醒或发送通知。
2. 业务权限以平台 `users.role`、启用状态和必要白名单为准;飞书身份只用于登录认证、身份匹配和操作人追溯。
3. 权限必须在 DRF permission class 和 service 层同时校验,不能只靠前端隐藏按钮或飞书卡片按钮。
4. 飞书登录回调、事件回调和卡片回调必须验签;验签失败不得执行业务动作。
5. 通知、飞书事件、卡片点击、草稿转换和提醒调度必须幂等。
6. 密钥、token、完整手机号、完整邮箱、一次性操作 token 和回调验签密钥不得进入普通日志。
7. 权限失败、验签失败、通知失败、回调失败、AI 解析失败、模型失败、未授权机器人访问和调度失败必须可复盘。
## 2. 身份边界
| 身份来源 | 可用于 | 不可用于 |
| --- | --- | --- |
| 平台用户 `users` | 登录态、角色、权限、数据范围 | 自然语言称呼解析 |
| `person_mappings` | 称呼、别名、飞书身份、第一批试用人员映射 | 替代平台登录和角色权限 |
| 飞书 open_id / user_id / union_id | 登录认证、消息接收人、回调操作人追溯 | 直接绕过平台权限 |
| AI 对话记忆表 | 上下文恢复、追问、草稿修订辅助 | 替代正式业务表、权限判断和人工确认 |
同一个真实人员如果可以登录平台,应在 `person_mappings.user_id` 绑定对应 `users.id`。飞书身份同步失败时写入 `failure_records`,不得静默降级成“默认有权限”。
## 3. 角色权限
| 动作 | 老板 | 程经理 | 普通员工 | 管理员 / AI 团队 |
| --- | --- | --- | --- | --- |
| 飞书登录 / 平台登录 | 可用 | 可用 | 可用 | 可用 |
| 飞书机器人私聊 AI 秘书 | 可用 | 不开放 | 不开放 | 仅测试或排查 |
| 确认、取消、补充自己的 AI 草稿 | 可用 | 仅待自己确认的复杂事项 | 不开放 | 仅排查,不代替业务确认 |
| 创建事项 | 可用 | 可用 | 不开放给别人 | 可测试或维护 |
| 创建自己的提醒 | 可用 | 可用 | 可用 | 可用 |
| 给别人创建提醒 | 可用 | 可用 | 不开放 | 可测试或维护 |
| 反馈事项或提醒 | 仅自己相关 | 仅自己相关 | 仅自己作为接收人 | 可测试,不代替业务反馈 |
| 查看失败记录原始信息 | 必要业务摘要 | 必要业务摘要 | 不开放 | 可用 |
| 维护人员映射和提示词版本 | 不开放 | 不开放 | 不开放 | 可用 |
普通员工给别人创建事项或提醒、非接收人反馈、非老板使用机器人入口,都必须拒绝并记录。
## 4. 人工确认边界
1. `task` / `reminder` AI 输出必须先生成可确认草稿。
2. 草稿确认前必须校验发起人、草稿状态、缺失字段、接收人映射和当前有效卡片。
3. `note` / `unknown` / `qa` / `realtime_qa` / `need_more_info` / `unsupported` 不得进入事项、提醒或通知闭环。
4. 复杂事项在老板确认后创建 `tasks.status=pending_manager_confirm` 的事项壳,由程经理补齐接收人、内容并触发通知。
5. 草稿、任务、提醒、通知的状态流转必须走 service 层;非法跳转返回 `state_conflict`
## 5. 飞书安全和幂等
1. 飞书回调必须先验签,再解析和执行业务动作。
2. `feishu_events.event_id` 应尽量唯一;没有稳定 `event_id` 时,用 `operator_open_id + action_value + notification_id + target_id` 生成 `idempotency_key` 并唯一约束。
3. 同一飞书事件只处理一次;重复事件只返回既有处理结果,不重复创建草稿、事项、提醒、反馈或通知。
4. 草稿确认类卡片必须匹配 `ai_drafts.active_card_notification_id`
5. 旧卡片、失效卡片、已取消草稿、已替代草稿、已过期草稿和已转换草稿的回调只记录 `feishu_events`,返回“该卡片已失效,请以最新卡片或平台详情为准”,不得写业务对象。
6. 接收人反馈类卡片重复点击必须幂等;是否覆盖最新反馈状态由 feedback service 明确处理,但每次有效点击都应可追溯。
7. 程经理待确认提醒在飞书里只跳转平台,不通过机器人对话分发事项。
## 6. 通知和操作 token
1. `notifications.idempotency_key` 必须按 `purpose` 生成并设置唯一约束。
2. 草稿确认卡片、程经理确认提醒、事项通知、提醒触发通知和补发通知应使用不同 `purpose`
3. 平台反馈页或一次性操作链接只能保存 token hash,例如 `action_token_hash`,不得保存明文 token。
4. 卡片或操作链接过期时写 `expires_at`,被新卡片替代或业务取消时写 `invalidated_at`
5. `expires_at``invalidated_at` 已生效后,回调不得继续确认、转换、通知或反馈,只能记录事件和提示失效。
## 7. 日志脱敏
普通应用日志、`operation_logs.summary``operation_logs.metadata``failure_records.raw_error` 和列表页摘要不得包含:
1. 阿里百炼 API Key。
2. 飞书 App Secret、tenant access token、user access token。
3. 飞书回调验签密钥。
4. OAuth code 和 OAuth state 的完整值。
5. 平台反馈页一次性操作 token 明文。
6. 完整手机号和完整邮箱。
7. 数据库账号密码和连接串密码。
8. 飞书原始 payload 中的敏感字段。
9. 模型 prompt 中的敏感全文或过长背景库全文。
手机号和邮箱在普通日志中只能脱敏展示。密钥、token、OAuth code/state、一次性操作 token 原文只能存在请求处理内存或安全配置中,不得落普通业务表。
## 8. 访问范围
| 信息 | 普通员工 | 老板 | 程经理 | 管理员 / AI 团队 |
| --- | --- | --- | --- | --- |
| 自己相关事项、提醒、反馈 | 可看摘要 | 可看相关记录 | 可看管理范围 | 可排查 |
| 失败记录列表 | 不可见 | 必要摘要 | 必要摘要 | 可见 |
| `model_call_logs` 原始请求和响应 | 不可见 | 不可见 | 不可见 | 可排查 |
| `feishu_events.raw_payload` | 不可见 | 不可见 | 不可见 | 可排查 |
| 完整手机号、邮箱 | 默认不可见 | 默认脱敏 | 默认脱敏 | 维护页按需可见 |
| 操作 token 明文 | 不可见 | 不可见 | 不可见 | 不可见 |
模型日志和飞书原始 payload 可以为排查保存必要结构,但必须通过接口权限和 Admin 权限限制访问,普通列表页默认只展示摘要。
## 9. 必须写失败记录的情况
1. AI 输出无法解析或不符合 schema。
2. 百炼或模型网关重试后仍失败。
3. PostgreSQL AI 记忆、消息或 BotContext 保存/读取失败。
4. 草稿确认后转换事项或提醒失败。
5. 缺少人员映射且阻断确认。
6. 普通员工越权创建事项或提醒。
7. 非接收人提交反馈。
8. 飞书登录失败、验签失败、事件格式异常或回调处理失败。
9. 飞书通知发送失败或补发失败。
10. 定时提醒触发失败或重复触发被跳过需要留痕。
11. 有问题反馈缺少原因。
如果 AI 解析失败时无法创建最小 `parse_failed` 草稿,`failure_records.target_type` 可以使用 `model_call_log` 并关联对应模型调用日志。
## 10. 必须测试的真实约束
每个 plan 完成前,必须说明新增或修改了哪些测试,以及这些测试覆盖了哪些 spec / contract 约定。第一版至少用测试卡住:
1. 权限:普通员工不能给别人创建事项或提醒。
2. 权限:非接收人不能反馈事项或提醒。
3. AI:AI 输出只能生成草稿,不能绕过人工确认直接执行。
4. AI`note` / `unknown` 不创建事项、提醒或通知。
5. 状态:草稿、事项、提醒不能非法跳转。
6. 飞书:回调必须验签,重复事件和重复点击必须幂等。
7. 飞书:旧卡片或失效卡片只记录事件,不写业务对象。
8. 定时:同一个提醒、同一触发时间、同一接收人、同一渠道不能重复触发通知。
9. 事务:通知失败时不能留下被误判为已通知的业务状态。
10. 日志:密钥、token、完整手机号、完整邮箱和一次性操作 token 不进普通日志。
## 11. 第一版不做
1. 不做复杂组织架构和多级 RBAC。
2. 不做独立审计平台。
3. 不做复杂安全合规体系。
4. 不让 AI 自动操作交易系统、公司历史数据库、后台管理系统或其他外部业务系统。
5. 不把飞书机器人开放为程经理或普通员工通用派活入口。
+75 -16
View File
@@ -50,6 +50,7 @@
| `aliases` | JSON 数组,例如 `["东东", "CJN"]` |
| `department` | 部门 |
| `business_role` | 业务角色,例如行政、程经理、下单员 |
| `manager_user_id` | 第一批试用管理范围归属,可选 |
| `phone` | 手机号,可选 |
| `email` | 邮箱,可选 |
| `feishu_open_id` | 飞书 open_id |
@@ -59,6 +60,13 @@
| `is_trial_user` | 是否第一批试用人员 |
| `note` | 备注 |
口径:
1. `users` 是登录、角色和权限判断的权威对象。
2. `person_mappings` 是自然语言称呼、手机号 / 邮箱、飞书身份和第一批试用人员映射的权威对象。
3. 同一个真实人员如果可以登录平台,应在 `person_mappings.user_id` 绑定对应 `users.id`
4. 程经理第一批试用管理范围先用 `manager_user_id` 或等价白名单表达,不引入完整组织树。
## 4. feishu_auth_sessions
记录飞书登录过程和平台用户绑定结果。
@@ -103,7 +111,7 @@
| `role` | `boss``assistant``system` |
| `text` | 原始输入或 AI 回复 |
| `answer` | AI 给老板的回复,可为空 |
| `intent_type` | `task``reminder``qa``realtime_qa``note``need_more_info``unsupported``context_summary` |
| `intent_type` | `task``reminder``qa``realtime_qa``note``need_more_info``unknown``unsupported``context_summary` |
| `draft_id` | 关联 AI 草稿,可为空 |
| `raw_payload` | 原始请求摘要,避免保存密钥 |
| `bot_context_snapshot` | JSONB,消息发生时的上下文快照 |
@@ -118,7 +126,7 @@
| `id` | 上下文 ID |
| `conversation_id` | 当前老板秘书会话 ID |
| `boss_id` | PostgreSQL 中的老板用户 ID |
| `status` | `empty``awaiting_more_info``awaiting_confirm``expired``cleared` |
| `status` | `empty``awaiting_more_info``awaiting_confirm``awaiting_follow_up``expired``cleared` |
| `pending_draft_id` | 当前待补充或待确认的草稿 ID |
| `pending_draft_type` | `task``reminder``none` |
| `last_intent` | 上一条有效意图 |
@@ -128,6 +136,8 @@
| `extracted_facts` | JSONB,从多轮对话提取的候选事实,只用于草稿修订 |
| `updated_at` | 上下文最近更新时间 |
`awaiting_follow_up` 专用于老板点击飞书确认卡片上的“补充/重说”后,等待下一条消息修订上一版草稿;该状态必须带 `pending_draft_id``expires_at`
## 8. prompt_contexts
维护 AI 秘书调用模型时加载的上下文版本。
@@ -153,26 +163,42 @@
| --- | --- |
| `id` | 草稿 ID |
| `source` | `platform``feishu_bot` |
| `source_message_id` | 飞书或外部入口消息 ID,可选 |
| `created_by` | 发起人 |
| `parent_draft_id` | 补充/重说生成新草稿时,关联上一版草稿 |
| `raw_input` | 原始输入 |
| `intent` | `task``reminder` |
| `draft_type` | `task``reminder` |
| `should_create_draft` | 固定为 true`qa/realtime_qa/note/need_more_info/unsupported` 不落 ai_drafts |
| `intent` | `task``reminder``qa``realtime_qa``note``need_more_info``unknown``unsupported` |
| `draft_type` | `task``reminder``none` |
| `should_create_draft` | `task/reminder` 为 true;其他意图必须为 false |
| `status` | 见状态流转约定 |
| `title` | 草稿标题 |
| `content` | 事项或提醒内容 |
| `receiver_candidates` | JSON,接收人候选和置信度 |
| `receiver_text` | 未映射成功时保留老板原始称呼 |
| `selected_receiver_id` | 人工确认后的接收人,可选 |
| `scheduled_at` | 提醒时间,可为空;任务可使用 `schedule_text` 表达期望时间 |
| `schedule_text` | 老板原始时间表达 |
| `recurrence_type` | `none``daily``weekly``monthly` |
| `requires_feedback` | 是否需要反馈 |
| `route_type` | `direct_after_boss_confirm``manager_confirm_required` |
| `route_type` | `none``direct_after_boss_confirm``manager_confirm_required` |
| `need_manager_confirm` | 是否需要程经理确认 |
| `missing_fields` | JSON 数组 |
| `questions` | JSON 数组,最多 3 个;确认草稿一般为空 |
| `answer` | 给老板看的回复摘要;不得写“已通知、已创建、已发送”等执行语义 |
| `active_card_notification_id` | 当前有效确认卡片通知,可选 |
| `superseded_by_draft_id` | 被补充/重说的新草稿替代时,指向新草稿 |
| `model_call_log_id` | 关联模型调用日志 |
| `confirmed_by` | 确认人 |
| `confirmed_at` | 确认时间 |
| `cancelled_reason` | 取消原因 |
草稿生命周期口径:
1. 每个草稿同一时间只能有一张有效确认卡片,对应 `active_card_notification_id`
2. 老板点击“补充/重说”后,原草稿进入 `awaiting_follow_up`,原确认卡片应失效。
3. 30 分钟内收到补充消息后,系统生成新草稿,`parent_draft_id` 指向原草稿;原草稿进入 `superseded``superseded_by_draft_id` 指向新草稿。
4. 超过 30 分钟仍未收到补充消息时,`bot_contexts` 标记为 `expired`,原草稿进入 `expired`;老板下一条消息按新输入处理。
5. `note` / `unknown` 不进入事项、提醒或通知闭环;如需留痕,只能保存回复、消息、模型调用日志或最小草稿记录。
## 10. model_call_logs
@@ -183,12 +209,17 @@
| `id` | 日志 ID |
| `provider` | `bailian` |
| `model_name` | 模型名 |
| `prompt_context_snapshot` | 本次使用的上下文版本和摘要 |
| `prompt_version` | 提示词版本 |
| `background_context_version` | 公司背景摘要版本 |
| `boss_style_version` | 老板沟通风格版本 |
| `ai_secretary_rules_version` | AI 秘书业务规则版本 |
| `context_snapshot` | 本次使用的上下文版本和摘要 |
| `input_text` | 用户原始输入 |
| `request_payload` | 请求摘要,避免保存密钥 |
| `raw_response` | 模型原始响应 |
| `parsed_json` | 解析后的 `AiSecretaryResponse` JSON |
| `response_payload` | 模型原始响应结构 |
| `parsed_result` | 后端解析后的 `AiSecretaryResponse` JSON |
| `status` | `success``failed` |
| `failure_reason` | 失败原因 |
| `error_message` | 失败原因 |
| `latency_ms` | 耗时 |
| `created_by` | 调用发起人 |
@@ -199,11 +230,12 @@
| 字段 | 说明 |
| --- | --- |
| `id` | 事项 ID |
| `source_type` | `ai_draft``manual` |
| `source_draft_id` | 来源草稿,可选 |
| `title` | 标题 |
| `content` | 事项内容 |
| `initiator_id` | 发起人 |
| `receiver_id` | 接收人 |
| `receiver_id` | 接收人;复杂事项在程经理确认前可为空 |
| `manager_id` | 复杂事项确认人,可选 |
| `status` | 见状态流转约定 |
| `visible_feedback_status` | `received``in_progress``completed``problem` |
@@ -214,6 +246,8 @@
| `completed_at` | 完成时间 |
| `problem_reason` | 有问题原因,可选 |
复杂事项必须在老板确认后创建 `status=pending_manager_confirm` 的事项壳,不得一直停留在草稿中等待程经理补齐。
## 12. reminders
未来某个时间触发的提醒。
@@ -221,6 +255,7 @@
| 字段 | 说明 |
| --- | --- |
| `id` | 提醒 ID |
| `source_type` | `ai_draft``manual` |
| `source_draft_id` | 来源草稿,可选 |
| `related_task_id` | 关联事项,可选 |
| `title` | 标题 |
@@ -244,16 +279,33 @@
| `id` | 通知 ID |
| `target_type` | `ai_draft``task``reminder``failure_record` |
| `target_id` | 关联对象 ID |
| `purpose` | `draft_confirm``manager_confirm``task_notify``reminder_trigger` |
| `receiver_id` | 接收人 |
| `channel` | `feishu``platform` |
| `channel` | `feishu_personal``feishu_group``platform` |
| `message_type` | `text``card` |
| `title` | 通知标题 |
| `summary` | 通知摘要 |
| `link_url` | 平台详情链接 |
| `status` | `pending``sent``failed``retrying``cancelled` |
| `idempotency_key` | 幂等键 |
| `card_id` | 飞书卡片 ID,可选 |
| `status` | `pending``sending``sent``failed``retrying``cancelled``expired` |
| `trigger_time` | 提醒触发时间;非提醒触发通知可为空 |
| `idempotency_key` | 幂等键,按通知目的生成并唯一约束 |
| `action_token_hash` | 平台反馈页一次性操作 token 的 hash,可选 |
| `feishu_message_id` | 飞书消息 ID |
| `sent_at` | 发送时间 |
| `expires_at` | 卡片或通知操作过期时间,可选 |
| `invalidated_at` | 被新卡片替代或业务取消时的失效时间,可选 |
| `failure_reason` | 失败原因 |
| `retry_count` | 重试次数 |
| `last_retry_at` | 最近重试时间 |
提醒触发类通知的幂等键格式建议:
```text
reminder:{reminder_id}:{trigger_time}:{receiver_id}:{channel}
```
任务通知、草稿确认卡片和程经理待确认提醒也必须生成各自的幂等键,避免重复点击、重复补发或回调重放造成多条有效通知。
## 14. feedbacks
@@ -289,6 +341,8 @@ AI、通知、回调、调度等失败原因。
| `handled_at` | 处理时间 |
| `handle_result` | 处理结果 |
`target_type` 可使用 `ai_draft``task``reminder``notification``feishu_event``model_call_log` 等实际对象类型。AI 解析失败时,如果无法创建最小 `parse_failed` 草稿,可关联 `model_call_log`
## 16. operation_logs
人工确认、修改、取消、补发等过程留痕。
@@ -317,6 +371,7 @@ AI、通知、回调、调度等失败原因。
| `event_type` | 事件类型 |
| `operator_open_id` | 操作人 open_id |
| `action_value` | 按钮值 |
| `idempotency_key` | 事件处理幂等键,可选 |
| `target_type` | `ai_draft``task``reminder``auth``bot` |
| `target_id` | 关联业务对象 |
| `notification_id` | 关联通知 |
@@ -325,9 +380,13 @@ AI、通知、回调、调度等失败原因。
| `process_result` | 处理结果 |
| `created_at` | 接收时间 |
`event_id` 应尽量设置唯一约束;如果飞书某类回调没有稳定 `event_id`,则用 `operator_open_id + action_value + notification_id + target_id` 生成 `idempotency_key` 并唯一约束。重复事件只返回已处理结果,不重复创建草稿、事项、提醒、反馈或通知。
## 18. 敏感字段规则
1. `phone``email` 可入库,但普通日志和普通列表接口默认脱敏。
2. API Key、App Secret、飞书 token 不得入库到业务表或普通日志。
3. `raw_error``metadata``request_payload` 必须避免密钥和完整个人联系方式。
2. API Key、App Secret、飞书 token、飞书回调验签密钥、OAuth code/state、一次性操作 token 明文不得入库到业务表或普通日志。
3. `raw_error``metadata``request_payload``response_payload` 必须避免密钥和完整个人联系方式。
4. 飞书通知内容只保存和发送摘要,不展开敏感全文。
5. `model_call_logs` 原始请求/响应和 `feishu_events.raw_payload` 仅供管理员 / AI 团队排查,普通员工不可见,老板和程经理只在必要业务范围内查看摘要。
6. 平台反馈页一次性操作 token 只能保存 hash,不能保存明文。
+41 -19
View File
@@ -14,31 +14,41 @@
| 状态 | 含义 |
| --- | --- |
| `PENDING_CONFIRM` | 草稿已整理完成,等待老板确认、修改、取消或转经理 |
| `NEED_MANAGER_CONFIRM` | 老板已确认,但复杂任务需要程经理确认 |
| `CONFIRMED` | 已人工确认,等待转换任务或提醒 |
| `CONVERTED` | 已转换为事项或提醒 |
| `CANCELLED` | 已取消 |
| `FAILED` | AI 解析或草稿处理失败,仅用于调试追踪 |
| `pending_confirmation` | 草稿已整理完成,等待老板确认、修改、取消或补充/重说 |
| `awaiting_follow_up` | 老板已点击补充/重说,等待 30 分钟内下一条补充消息 |
| `confirmed` | 已人工确认,等待转换为事项或提醒 |
| `converted` | 已转换为事项或提醒;复杂事项已创建 `pending_manager_confirm` 事项壳也视为已转换 |
| `cancelled` | 已取消 |
| `answered` | 已直接回复或留痕,不进入事项、提醒或通知闭环 |
| `superseded` | 已被补充/重说生成的新草稿替代 |
| `expired` | 补充/重说等待超时,旧确认卡片不可继续使用 |
| `parse_failed` | AI 解析或草稿处理失败,仅用于调试追踪 |
允许流转:
```text
PENDING_CONFIRM -> CONFIRMED
PENDING_CONFIRM -> NEED_MANAGER_CONFIRM
PENDING_CONFIRM -> CANCELLED
PENDING_CONFIRM -> FAILED
NEED_MANAGER_CONFIRM -> CONFIRMED
CONFIRMED -> CONVERTED
CONFIRMED -> FAILED
pending_confirmation -> confirmed
pending_confirmation -> awaiting_follow_up
pending_confirmation -> cancelled
pending_confirmation -> parse_failed
awaiting_follow_up -> superseded
awaiting_follow_up -> expired
awaiting_follow_up -> cancelled
confirmed -> converted
confirmed -> parse_failed
```
约束:
1. `FAILED``CANCELLED``CONVERTED` 为终态。
2. 未进入 `CONFIRMED` 的草稿不得转换。
3. `need_more_info``answered``context_summary` 是非草稿处理结果,不落 `ai_drafts.status`
4. 多次补充/重说优先合并到同一个 `pending_draft`,直到老板确认、取消、过期或明确新建,不要求每次补充都新建草稿
1. `parse_failed``cancelled``converted``answered``superseded``expired` 为终态。
2. 未进入 `confirmed` 的草稿不得转换。
3. `note``unknown``qa``realtime_qa``need_more_info``unsupported` 不进入事项、提醒或通知闭环;如需留痕,只能使用 `answered``parse_failed`
4. 老板点击补充/重说后,原草稿进入 `awaiting_follow_up`,并使原确认卡片失效
5. 30 分钟内收到补充消息后,新草稿 `parent_draft_id` 指向原草稿,原草稿进入 `superseded`
6. 超过 30 分钟后,原草稿进入 `expired`,老板下一条消息按新输入处理。
7. 飞书回调确认、取消、补充/重说前,必须校验回调来自 `active_card_notification_id` 对应的当前有效卡片。
8. 复杂事项不使用草稿状态等待程经理确认;老板确认后应创建 `tasks.status=pending_manager_confirm` 的事项壳。
9. `answered``parse_failed` 可以作为最小留痕草稿的初始终态,不参与确认和转换。
## 3. 事项状态
@@ -144,21 +154,28 @@ triggered -> expired
| 状态 | 含义 |
| --- | --- |
| `pending` | 待发送 |
| `sending` | 发送中 |
| `sent` | 已发送 |
| `failed` | 发送失败 |
| `retrying` | 补发中 |
| `cancelled` | 已取消 |
| `expired` | 卡片或通知操作已过期 |
允许流转:
```text
pending -> sending
pending -> sent
pending -> failed
sending -> sent
sending -> failed
failed -> retrying
retrying -> sent
retrying -> failed
pending -> cancelled
failed -> cancelled
pending -> expired
sent -> expired
```
约束:
@@ -166,6 +183,8 @@ failed -> cancelled
1. `sent` 不得重复发送同一通知。
2. 补发必须保留原失败记录和新发送结果。
3. 飞书通知必须使用幂等键。
4. `expired``cancelled` 通知不得继续处理卡片确认、反馈或补充/重说。
5. 被新卡片替代或业务取消时,必须写 `invalidated_at`
通知幂等键建议:
@@ -252,8 +271,10 @@ processing -> cancelled
约束:
1. 同一 `event_id` 只处理一次。
2. 验签失败不得执行业务动作
3. 处理失败必须写失败记录
2. 如果飞书某类回调没有稳定 `event_id`,必须使用 `idempotency_key` 做事件幂等
3. 验签失败不得执行业务动作
4. 旧卡片、失效卡片、已替代草稿和已过期通知的回调只记录事件并标记 `ignored`,不得写业务对象。
5. 处理失败必须写失败记录。
## 12. 用户和配置状态
@@ -295,5 +316,6 @@ Prompt 上下文状态:
| `empty` | 当前没有可补充或可确认的草稿 |
| `awaiting_more_info` | AI 已追问,等待老板补充 |
| `awaiting_confirm` | 草稿已整理完成,等待老板确认、修改、取消或转经理 |
| `awaiting_follow_up` | 老板点击补充/重说后,等待下一条消息修订上一版草稿 |
| `expired` | 已过期 |
| `cleared` | 草稿已确认、取消或转换完成,上下文已清空 |
@@ -22,8 +22,9 @@
8. `docs/contracts/API接口约定.md`
9. `docs/contracts/数据对象约定.md`
10. `docs/contracts/状态流转约定.md`
11. `docs/checklists/AI生成代码检查清单.md`
12. `docs/checklists/后端Review清单.md`
11. `docs/contracts/安全权限日志约定.md`
12. `docs/checklists/AI生成代码检查清单.md`
13. `docs/checklists/后端Review清单.md`
## 范围边界
@@ -47,6 +48,7 @@
2. 真实联调前必须确认飞书正式应用、HTTPS 回调地址、个人消息权限、交互卡片权限、老板 / 程经理 / 第一批试用人员映射,以及百炼 API Key 环境变量。
3. Docker / 部署环境固定 Python 3.12.13;本地开发允许 Python 3.11 或 3.12,但提交前必须在 Python 3.12.13 环境跑完整测试。
4. scheduler 必须作为独立进程运行,同一环境同一应用库同时只能启动一个 scheduler 进程。
5. tests 是真实约束;每个任务完成前必须说明新增或修改了哪些测试、覆盖哪些 spec / contract 约定和如何运行。
## 文件结构
@@ -202,7 +204,7 @@ Expected: `System check identified no issues`。
- [ ] **Step 2: 实现 PersonMapping**
字段必须覆盖 `person_mappings`,并支持别名 JSON。
字段必须覆盖 `person_mappings`,并支持别名 JSON`user_id``manager_user_id``users` 负责登录和权限,`person_mappings` 负责称呼、飞书身份和第一批试用人员映射
- [ ] **Step 3: 实现 PromptContext**
@@ -255,16 +257,16 @@ Expected: migrations 成功,权限、审计和 AI 记忆测试通过。
- [ ] **Step 1: 实现 AiDraft 模型和 AiSecretaryResponse 结构**
字段必须覆盖 `ai_drafts`,状态必须来自 `状态流转约定.md``qa/realtime_qa/note/need_more_info/unsupported/context_summary``ai_drafts`,只写 `secretary_messages``model_call_logs`必要 `operation_logs`
字段必须覆盖 `ai_drafts`,状态必须来自 `状态流转约定.md``qa/realtime_qa/note/need_more_info/unknown/unsupported/context_summary`进入事项、提醒或通知闭环,只写 `secretary_messages``model_call_logs`必要 `operation_logs`,或按 contract 写最小留痕草稿
- [ ] **Step 2: 实现 AI 输出 validator**
validator 必须校验:
1. JSON 可解析。
2. `intent` 只能是 `task/reminder/qa/realtime_qa/note/need_more_info/unsupported`
2. `intent` 只能是 `task/reminder/qa/realtime_qa/note/need_more_info/unknown/unsupported`
3. `task` / `reminder` 才允许 `should_create_draft = true``draft_type` 必须对应为 `task``reminder`
4. `qa/realtime_qa/note/need_more_info/unsupported` 必须 `should_create_draft=false``draft_type=none`
4. `qa/realtime_qa/note/need_more_info/unknown/unsupported` 必须 `should_create_draft=false``draft_type=none`
5. `need_more_info``questions` 至少 1 个、最多 3 个;其他 intent 时 `questions` 为空数组。
6. `task``route_type` 只能是 `direct_after_boss_confirm``manager_confirm_required`,其他 intent 为 `none`
7. `answer` 不得出现“已通知、已创建、已发送”等执行语义。
@@ -283,7 +285,7 @@ service 流程:
-> 按 source + message_id 做幂等检查
-> 校验老板角色
-> 读取 BotContext
-> 判断 follow_up / new_request / qa / realtime_qa / note / unsupported / command
-> 判断 follow_up / new_request / qa / realtime_qa / note / unknown / unsupported / command
-> 组装 PromptContext
-> 调用 ai_client
-> 写 model_call_logs
@@ -336,7 +338,7 @@ Expected: AI 输出校验、解析失败、实时问答、补充/重说测试全
- [ ] **Step 1: 实现模型**
字段必须覆盖 `tasks``reminders``notifications``feedbacks`
字段必须覆盖 `tasks``reminders``notifications``feedbacks``notifications` 必须包含 `target_type=ai_draft``purpose``trigger_time``idempotency_key``action_token_hash``expires_at``invalidated_at` 等字段。
- [ ] **Step 2: 实现事项 service**
@@ -409,7 +411,7 @@ Expected: 权限、状态、反馈原因和通知失败测试通过。
- [ ] **Step 3: 实现事件幂等**
同一 `event_id` 重放时只返回已有处理结果,不重复创建草稿、事项、提醒或反馈。
同一 `event_id` 重放时只返回已有处理结果,不重复创建草稿、事项、提醒或反馈。没有稳定 `event_id` 的卡片回调必须使用 `idempotency_key`
- [ ] **Step 4: 实现老板机器人入口**
@@ -431,6 +433,7 @@ Expected: 权限、状态、反馈原因和通知失败测试通过。
3. 老板取消草稿。
4. 接收人反馈事项或提醒。
5. 程经理待确认提醒只跳转平台。
6. 旧卡片、失效卡片、已替代草稿和已过期通知只记录 `feishu_events`,不写业务对象。
- [ ] **Step 6: 跑测试**
@@ -458,11 +461,11 @@ Expected: 验签、幂等、非老板访问、卡片反馈测试通过。
- [ ] **Step 1: 实现草稿确认和转换**
`CONFIRMED -> CONVERTED` 只允许执行一次。`task` 草稿转换为事项,`reminder` 草稿转换为提醒。转换失败时不回滚老板确认,写 `FailureRecord(draft_convert_failed)`
`confirmed -> converted` 只允许执行一次。`task` 草稿转换为事项,`reminder` 草稿转换为提醒。转换失败时不回滚老板确认,写 `FailureRecord(draft_convert_failed)`
- [ ] **Step 2: 实现复杂事项转程经理确认**
`route_type = manager_confirm_required` 时,草稿进入 `NEED_MANAGER_CONFIRM`,创建程经理待确认记录,并生成给程经理的飞书提醒;程经理确认后才进入 `CONFIRMED` 并转换任务
`route_type = manager_confirm_required` 时,老板确认后草稿进入 `confirmed`,转换时先创建 `tasks.status=pending_manager_confirm` 的事项壳,并生成给程经理的飞书提醒;事项壳创建成功后草稿进入 `converted`。程经理确认事项壳后补齐接收人和内容,再进入通知流程
- [ ] **Step 3: 实现 scheduler command**
@@ -590,7 +593,9 @@ Expected: 全部测试通过,Django check 无错误。
7. 定时提醒重复触发防护有测试。
8. 通知失败、AI 解析失败、回调失败有失败记录。
9. 普通日志不包含密钥、token、完整手机号或完整邮箱。
10. `python manage.py test``python manage.py check` 通过
10. OAuth code/state、回调验签密钥、一次性操作 token 明文不进入普通日志或业务表
11. 每个任务完成前说明新增或修改了哪些 tests、覆盖哪些 spec / contract 约定和如何运行。
12. `python manage.py test``python manage.py check` 通过。
## 执行备注
+67 -51
View File
@@ -15,7 +15,7 @@
```text
老板一句话
-> AiSecretaryAgent 判断意图和上下文
-> 意图归类为 task / reminder / qa / realtime_qa / note / need_more_info / unsupported
-> 意图归类为 task / reminder / qa / realtime_qa / note / need_more_info / unknown / unsupported
-> 稳定 AiSecretaryResponse JSON
-> 生成 AI 草稿 / 追问 / 直接回复
-> 老板确认、补充、取消或转程经理
@@ -23,7 +23,7 @@
- 支持老板通过飞书私聊或平台入口输入文本。
- 统一走 `POST /api/secretary/handle-message`
- 判断 `task/reminder/qa/realtime_qa/note/need_more_info/unsupported`
- 判断 `task/reminder/qa/realtime_qa/note/need_more_info/unknown/unsupported`
- 生成稳定的 `AiSecretaryResponse` JSON。
- 对任务和提醒只生成可确认草稿,不直接执行。
- 对普通聊天、实时问答兜底、普通记录直接回复或记录。
@@ -62,7 +62,7 @@ POST /api/secretary/handle-message
-> 校验发送人角色
-> 非老板直接固定回复并记录
-> 读取 BotContext,外部 context 只作为参考
-> 判断本次消息是 follow_up / new_request / qa / realtime_qa / note / unsupported / command
-> 判断本次消息是 follow_up / new_request / qa / realtime_qa / note / unknown / unsupported / command
-> 组装 PromptContext
-> 调用模型
-> 校验 AiSecretaryResponse
@@ -105,7 +105,7 @@ cleared
- 每次进入 `awaiting_more_info``awaiting_confirm` 状态时重置 `expires_at = now + 30 分钟`
- 点击或说出“补充/重说”后,30 分钟内下一条消息优先尝试作为上一条草稿的修正。
- 30 分钟内不能无条件吞掉所有消息,必须先判断为 `follow_up / new_request / qa / realtime_qa / note / unsupported / command`
- 30 分钟内不能无条件吞掉所有消息,必须先判断为 `follow_up / new_request / qa / realtime_qa / note / unknown / unsupported / command`
- 老板说“换成东东”“改到明天”“刚才那个不要了”,优先修正当前草稿。
- 老板说“另外再安排一个”“新建一个”“再帮我记一个”,创建新草稿。
- 老板说“确认”“就这样”“发给他”,确认当前草稿。
@@ -113,7 +113,7 @@ cleared
- 老板问“刚才我说了什么”“总结一下”,做上下文回顾,不修改草稿。
- 超过 30 分钟,上下文标记 `expired`,下一条默认按新输入处理;如果老板明显仍在说上一条草稿,可先提示“上一条草稿已过期,我按新请求处理”。
- 老板确认、取消或草稿转换为任务/提醒后立即清空上下文,不受 30 分钟窗口约束。
- 30 分钟窗口只绑定未确认草稿;草稿进入 `CONFIRMED / CONVERTED / CANCELLED` 后,后续“改一下刚才那个”不能再修改原草稿,只能作为已发布任务/提醒变更请求或第一阶段能力外请求处理。
- 30 分钟窗口只绑定未确认草稿;草稿进入 `confirmed / converted / cancelled / superseded / expired` 后,后续“改一下刚才那个”不能再修改原草稿,只能作为已发布任务/提醒变更请求或第一阶段能力外请求处理。
- 多次 `follow_up` 必须合并到同一个 `pending_draft`,直到老板确认、取消、过期或明确新建,不得每次补充都新建草稿。
- `BotContext` 只提供上下文候选,不强制绑定消息。当本地规则或模型无法判断是修改当前草稿、新建请求、普通聊天还是取消当前草稿时,必须走 `need_more_info` 追问,不允许默认修改当前草稿。
- 老板可以通过“另外、重新来、换个事、先别管这个、取消、重新安排”等自然语言随时跳出当前上下文。
@@ -143,7 +143,7 @@ cleared
- 3 轮仍补不全,也生成带 `missing_fields` 的草稿,不继续拉扯。
- 每次追问都必须保存一条 `SecretaryMessage``intent_type=need_more_info`,内容为本次给老板的问题。
- 追问轮次写入 `BotContext.follow_up_count`,并记录 `OperationLog(action=ask_follow_up)`
- `follow_up_count` 在以下情况重置为 0:草稿被确认、取消、过期、进入 `PENDING_CONFIRM`,或生成带 `missing_fields` 的终态草稿后收到下一条新消息。
- `follow_up_count` 在以下情况重置为 0:草稿被确认、取消、过期、进入 `pending_confirmation`,或生成带 `missing_fields` 的终态草稿后收到下一条新消息。
## 5. 数据对象
@@ -224,7 +224,7 @@ PostgreSQL 最小保存三类 AI 记忆数据:
"message_id": "外部消息ID或内部生成ID",
"role": "boss|assistant|system",
"text": "原始输入或AI回复",
"intent": "task|reminder|qa|realtime_qa|note|need_more_info|unsupported|context_summary",
"intent": "task|reminder|qa|realtime_qa|note|need_more_info|unknown|unsupported|context_summary",
"draft_id": "postgres_ai_draft_id",
"bot_context_snapshot": {},
"created_at": "2026-06-22T10:00:00+08:00"
@@ -246,7 +246,7 @@ PostgreSQL 最小保存三类 AI 记忆数据:
```json
{
"intent": "task|reminder|qa|realtime_qa|note|need_more_info|unsupported",
"intent": "task|reminder|qa|realtime_qa|note|need_more_info|unknown|unsupported",
"draft_type": "task|reminder|none",
"should_create_draft": true,
"route_type": "none|direct_after_boss_confirm|manager_confirm_required",
@@ -278,9 +278,9 @@ PostgreSQL 最小保存三类 AI 记忆数据:
校验规则:
- `task/reminder` 必须 `should_create_draft=true``draft_type` 必须对应为 `task``reminder`
- `qa/realtime_qa/note/need_more_info/unsupported` 必须 `should_create_draft=false``draft_type=none`
- `qa/realtime_qa/note/need_more_info/unknown/unsupported` 必须 `should_create_draft=false``draft_type=none`
- `task``route_type` 必填,只能是 `direct_after_boss_confirm``manager_confirm_required`
- `reminder/qa/realtime_qa/note/need_more_info/unsupported``route_type=none`
- `reminder/qa/realtime_qa/note/need_more_info/unknown/unsupported``route_type=none`
- `route_type=manager_confirm_required` 时,`draft.need_manager_confirm=true`;老板主动说“给程经理看看”“让经理确认”时,也必须置为 true。
- `route_type=direct_after_boss_confirm` 时,`draft.need_manager_confirm=false`
- `task` 必须有事项内容;时间不是必填项。
@@ -314,6 +314,7 @@ PostgreSQL 最小保存三类 AI 记忆数据:
| realtime_qa | 固定提示需要实时数据查询,第一版暂不作为正式能力 |
| note | 只保存消息记录,不生成草稿、不通知,可用于“刚才说了什么”回顾 |
| need_more_info | 信息不足但属于可处理范围,主动追问 |
| unknown | 暂时无法判断意图,只允许澄清或留痕 |
| unsupported | 超出第一版能力,解释为什么不能做,不追问执行字段 |
优先级和兜底规则:
@@ -322,6 +323,7 @@ PostgreSQL 最小保存三类 AI 记忆数据:
- `realtime_qa.answer` 固定使用模板:“这个需要实时数据查询,第一版暂不作为正式能力;我不会把它生成任务或提醒。”
- 超出第一版能力但不属于实时数据类的输入,归为 `unsupported`
- `unsupported.answer` 应说明当前入口只处理老板事项草稿、提醒草稿、普通问答和普通记录,不追问执行字段。
- `unknown.answer` 只能澄清或提示补充,不得创建事项、提醒或通知。
示例:
@@ -332,37 +334,51 @@ PostgreSQL 最小保存三类 AI 记忆数据:
### 5.4 草稿状态
`ai_drafts.status` 只表示已经落 PostgreSQL 草稿表的草稿状态。非草稿意图(`qa/realtime_qa/note/unsupported`)和纯追问结果(`need_more_info`)不`ai_drafts`,只保存 `SecretaryMessage``BotContext` 和处理结果
`ai_drafts.status` 只表示已经落 PostgreSQL 草稿表的草稿状态。非执行意图(`qa/realtime_qa/note/unknown/unsupported`)和纯追问结果(`need_more_info`)不得进入事项、提醒或通知闭环;如需留痕,只保存 `SecretaryMessage``BotContext``model_call_logs` 或最小草稿记录
`ai_drafts.status` 建议状态:
`ai_drafts.status` `docs/contracts/状态流转约定.md` 为准,核心状态:
```text
PENDING_CONFIRM
NEED_MANAGER_CONFIRM
CONFIRMED
CANCELLED
FAILED
CONVERTED
pending_confirmation
awaiting_follow_up
confirmed
converted
cancelled
answered
superseded
expired
parse_failed
```
对老板可见时可以简化为:待确认、待程经理确认、已确认、已取消、已失败。
对老板可见时可以简化为:待确认、等待补充、已确认、已取消、已失效、已完成、已失败。
状态含义:
- `PENDING_CONFIRM`:草稿可确认,等待老板确认。
- `NEED_MANAGER_CONFIRM`:老板已确认,但该任务仍需要程经理确认,不能直接转换任务
- `CONFIRMED`:确认链路已完成,可以交给任务或提醒模块转换。
- `CANCELLED`:老板取消草稿
- `FAILED`:草稿或 AI 解析过程失败的草稿状态;失败原因写入 `FailureRecord.failure_type`,例如 `ai_parse_failed`,两者不能混用
- `CONVERTED`:草稿已经成功转换为任务或提醒。
- `pending_confirmation`:草稿可确认,等待老板确认、补充/重说或取消
- `awaiting_follow_up`:老板点击补充/重说后,等待 30 分钟内下一条消息
- `confirmed`:确认链路已完成,可以交给任务或提醒模块转换。
- `converted`:草稿已经成功转换为任务或提醒;复杂事项已创建 `pending_manager_confirm` 事项壳也视为转换完成
- `cancelled`:老板取消草稿
- `answered`:已直接回复或留痕,不转换为任务或提醒。
- `superseded`:已被补充/重说生成的新草稿替代。
- `expired`:补充/重说等待超时,旧确认卡片不可继续使用。
- `parse_failed`:草稿或 AI 解析过程失败,仅用于调试追踪。
非草稿处理状态:
- `need_more_info`:本轮只追问老板,不创建 `ai_drafts`;追问内容保存为 `SecretaryMessage(intent_type=need_more_info)`,上下文状态为 `BotContext.status=awaiting_more_info`
- `answered``qa/realtime_qa/note/context_summary/unsupported` 的处理结果,不转换为任务或提醒,不影响当前有效 `BotContext`
- 追问 3 轮仍补不全、但系统决定生成带 `missing_fields` 的草稿时,模型输出必须从 `need_more_info` 转为 `task``reminder`,创建 `PENDING_CONFIRM` 草稿,并由确认阻塞规则控制能否确认。
- `answered``qa/realtime_qa/note/context_summary/unknown/unsupported` 的处理结果,不转换为任务或提醒,不影响当前有效 `BotContext`
- 追问 3 轮仍补不全、但系统决定生成带 `missing_fields` 的草稿时,模型输出必须从 `need_more_info` 转为 `task``reminder`,创建 `pending_confirmation` 草稿,并由确认阻塞规则控制能否确认。
`DRAFTING` 不作为第一版 `ai_drafts.status` 持久化枚举。实现中如需表达“AI 已决定生成草稿但还在校验/解析”的过程态,只能作为方法内的内存标记或局部变量;落库状态必须`PENDING_CONFIRM``NEED_MANAGER_CONFIRM``FAILED` 开始
`DRAFTING` 不作为第一版 `ai_drafts.status` 持久化枚举。实现中如需表达“AI 已决定生成草稿但还在校验/解析”的过程态,只能作为方法内的内存标记或局部变量;落库状态必须来自 `状态流转约定.md`
草稿确认卡片生命周期:
- 每个草稿同一时间只能有一张有效确认卡片,对应 `active_card_notification_id`
- 老板点击“补充/重说”后,原草稿进入 `awaiting_follow_up`,原确认卡片应失效。
- 30 分钟内收到补充消息后,新草稿 `parent_draft_id` 指向原草稿,原草稿进入 `superseded`
- 超过 30 分钟仍未收到补充消息时,原草稿进入 `expired`,老板下一条消息按新输入处理。
- 飞书回调处理确认、取消、补充/重说前,必须校验回调来自当前有效卡片;旧卡片只记录事件,不写业务对象。
### 5.5 Prompt 硬规则
@@ -467,25 +483,25 @@ POST /api/secretary/handle-message
-> 老板补充
-> 新草稿或修订草稿
-> 老板确认
-> direct_after_boss_confirm: CONFIRMED -> TaskAdapter / ReminderService 转换 -> CONVERTED
-> manager_confirm_required: NEED_MANAGER_CONFIRM -> 程经理确认 -> CONFIRMED -> TaskAdapter 转换 -> CONVERTED
-> direct_after_boss_confirm: confirmed -> TaskAdapter / ReminderService 转换 -> converted
-> manager_confirm_required: confirmed -> 创建 pending_manager_confirm 事项壳 -> converted -> 程经理确认后通知接收人
```
草稿确认后的边界:
- `direct_after_boss_confirm`:老板确认成功后,草稿进入 `CONFIRMED`
- `manager_confirm_required`:老板确认成功后,草稿进入 `NEED_MANAGER_CONFIRM`,通知或展示给程经理确认;程经理确认后才进入 `CONFIRMED`
- 任务/提醒转换成功后,草稿进入 `CONVERTED`,并清空 `BotContext`
- 任务/提醒转换失败时,不回滚老板确认;草稿保持 `CONFIRMED`,写 `FailureRecord(draft_convert_failed)`,支持后台重试或人工处理。
- 老板确认后,无论进入 `CONFIRMED` 还是 `NEED_MANAGER_CONFIRM`,都要清空老板侧 `BotContext`;后续经理确认不再依赖老板 30 分钟上下文。
- `NEED_MANAGER_CONFIRM / CONFIRMED / CONVERTED` 后,01 不再直接修改原草稿;老板后续说“把刚才发出去那个改一下”,应识别为任务/提醒变更请求,第一阶段未接入变更能力时回复暂不支持或引导到任务模块。
- `direct_after_boss_confirm`:老板确认成功后,草稿进入 `confirmed`,转换成功后进入 `converted`
- `manager_confirm_required`:老板确认成功后,草稿进入 `confirmed`;转换时先创建 `tasks.status=pending_manager_confirm` 的事项壳,并给程经理发送待确认提醒;事项壳创建成功后草稿进入 `converted`
- 程经理确认复杂事项时,补齐接收人、内容和通知信息,使事项进入 `pending_notify`,再通知接收人
- 任务/提醒转换失败时,不回滚老板确认;草稿保持 `confirmed`,写 `FailureRecord(draft_convert_failed)`,支持后台重试或人工处理。
- 老板确认后要清空老板侧 `BotContext`;后续经理确认不再依赖老板 30 分钟上下文。
- `confirmed / converted / cancelled / superseded / expired` 后,01 不再直接修改原草稿;老板后续说“把刚才发出去那个改一下”,应识别为任务/提醒变更请求,第一阶段未接入变更能力时回复暂不支持或引导到任务模块。
直接回复流转:
```text
qa / realtime_qa / note / context_summary
qa / realtime_qa / note / unknown / context_summary
-> answered 处理结果
-> 不落 ai_drafts,不转换,不清空当前有效 BotContext
-> 不进入事项、提醒或通知闭环,不清空当前有效 BotContext
```
`context_summary` 也必须保存为一条 `SecretaryMessage(intent_type=context_summary)`,便于调试页回看“老板什么时候要求回顾、系统回了什么”。
@@ -500,17 +516,17 @@ qa / realtime_qa / note / context_summary
JSON 非法 / schema 校验失败
-> 不创建可确认草稿
-> 可选创建 FAILED 草稿仅用于调试
-> 可选创建 parse_failed 草稿仅用于调试;如果无法创建最小草稿,failure_records.target_type 使用 model_call_log
-> FailureRecord(ai_parse_failed)
```
`FAILED` 草稿不能确认、不能转换为任务/提醒、不能发送通知,只能用于调试追踪。
`parse_failed` 草稿不能确认、不能转换为任务/提醒、不能发送通知,只能用于调试追踪。
取消流转:
```text
PENDING_CONFIRM
-> CANCELLED
pending_confirmation
-> cancelled
-> 清空 BotContext
awaiting_more_info 且还没有 ai_drafts
@@ -518,7 +534,7 @@ awaiting_more_info 且还没有 ai_drafts
-> 记录 OperationLog(cancel_context)
```
`NEED_MANAGER_CONFIRM` 阶段如需取消或退回,归程经理确认模块或任务模块处理;01 只负责展示状态和记录操作,不再把它当作可补充草稿处理。
复杂事项壳 `pending_manager_confirm` 阶段如需取消或退回,归程经理确认模块或任务模块处理;01 只负责展示草稿来源和记录操作,不再把它当作可补充草稿处理。
## 9. 失败和日志
@@ -756,7 +772,7 @@ awaiting_more_info 且还没有 ai_drafts
- 返回 `reply_type=error`
- `answer=暂时没处理好,请稍后再试。`
- `failure.type=ai_parse_failed`
- 如落 `FAILED` 草稿,仅用于调试,不能确认或转换。
- 如落 `parse_failed` 草稿,仅用于调试,不能确认或转换。
### 10.15 低置信度追问
@@ -776,7 +792,7 @@ awaiting_more_info 且还没有 ai_drafts
### 10.16 已发布后修改
前置:上一条草稿已经 `CONVERTED``BotContext` 已清空。
前置:上一条草稿已经 `converted``BotContext` 已清空。
输入:
@@ -864,10 +880,10 @@ awaiting_more_info 且还没有 ai_drafts
期望:
- 老板确认后草稿进入 `NEED_MANAGER_CONFIRM`
- 老板确认后草稿进入 `confirmed`
- 清空老板侧 `BotContext`
- 不直接调用 `TaskAdapter` 转换任务
- 程经理确认后草稿进入 `CONFIRMED`,再转换为任务并进入 `CONVERTED`
- 创建 `tasks.status=pending_manager_confirm` 的事项壳,不直接通知最终接收人
- 程经理确认后,事项壳进入 `pending_notify` 并通知接收人;草稿在事项壳创建成功后进入 `converted`
## 11. Review 重点
@@ -880,7 +896,7 @@ awaiting_more_info 且还没有 ai_drafts
- 是否低置信度时追问老板,而不是默认修改当前草稿。
- 是否 `ai_drafts.status` 和非草稿处理状态已经拆清,`need_more_info/answered` 不误落草稿表。
- 是否所有 AI 输出都做 JSON 校验。
- 是否 `qa/realtime_qa/note/need_more_info/unsupported` 没有误生成草稿
- 是否 `qa/realtime_qa/note/need_more_info/unknown/unsupported` 没有误进入事项、提醒或通知闭环
- 是否普通任务不强制时间。
- 是否提醒缺时间会追问。
- 是否 AI 只生成草稿,不直接执行。
@@ -907,8 +923,8 @@ awaiting_more_info 且还没有 ai_drafts
- 是否 `secretary_messages` 只追加、`bot_contexts` 可覆盖、`bot_context_snapshot` 不作为最新状态。
- 是否 PostgreSQL 作为唯一事实源,AI 记忆表不替代正式草稿、任务、提醒、通知和反馈表状态。
- 是否 PostgreSQL AI 记忆读写异常时会降级并写 `FailureRecord(memory_store_failed)` 或应用日志/告警。
- 是否草稿 `CONFIRMED / CONVERTED / CANCELLED` 后立即清空上下文,后续修改不再直接改原草稿。
- 是否 `manager_confirm_required``NEED_MANAGER_CONFIRM -> 程经理确认 -> CONFIRMED -> CONVERTED`,不会绕过程经理确认。
- 是否草稿 `confirmed / converted / cancelled / superseded / expired` 后立即清空或失效上下文,后续修改不再直接改原草稿。
- 是否 `manager_confirm_required``confirmed -> 创建 pending_manager_confirm 事项壳 -> converted -> 程经理确认事项壳 -> pending_notify`,不会绕过程经理确认。
- 是否转换失败写 `FailureRecord(draft_convert_failed)`,且不回滚老板确认。
- 是否上下文回顾默认范围为最近 10 条消息、当前待确认草稿和最近 3 条已确认/已转换草稿。
- 是否 `/api/feishu/callback` 的验签、去重和鉴权没有写进本模块。
+7 -7
View File
@@ -6,8 +6,8 @@
## 2. 第一版做什么
1. 事项可以来自 AI 草稿 `CONFIRMED` 后转换。
2. 事项可以由程经理确认复杂草稿后生成,复杂草稿来自 `route_type=manager_confirm_required`
1. 事项可以来自 AI 草稿 `confirmed` 后转换。
2. 复杂事项来自 `route_type=manager_confirm_required`,老板确认后先创建 `pending_manager_confirm` 的事项壳
3. 事项可以由老板或程经理手动创建。
4. 普通员工第一版不默认给别人创建事项。
5. 事项必须有发起人、接收人、事项内容、反馈要求和状态。
@@ -36,7 +36,7 @@
```text
AI 草稿或手动输入
-> 人工确认
-> AI 草稿进入 CONFIRMED
-> AI 草稿进入 confirmed
-> 创建 tasks
-> 创建 notifications
-> 飞书通知接收人
@@ -49,11 +49,11 @@ AI 草稿或手动输入
```text
老板确认转程经理
-> AI 草稿进入 NEED_MANAGER_CONFIRM
-> 创建 pending_manager_confirm 事项或待确认记录
-> AI 草稿进入 confirmed
-> 创建 pending_manager_confirm 事项壳,接收人和内容可由程经理补齐
-> 给程经理发飞书提醒
-> 程经理进入平台确认、修改和分发
-> 草稿进入 CONFIRMED 后转换任务
-> 事项壳进入 pending_notify
-> 通知接收人
-> 接收反馈
```
@@ -101,7 +101,7 @@ AI 草稿或手动输入
1. 未确认事项不得通知接收人。
2. `pending_manager_confirm` 必须由程经理确认后才能进入通知流程。
3. 01 模块中的 `NEED_MANAGER_CONFIRM` 不能绕过程经理直接创建正式任务
3. `manager_confirm_required` 不能绕过程经理直接通知最终接收人;必须先创建 `pending_manager_confirm` 事项壳
4. 通知失败进入 `notify_failed`,不得假装已通知。
5. 已取消事项不能反馈、通知或补发。
6. 有问题反馈会将事项标记为 `problem`,并记录原因。
+28 -8
View File
@@ -16,6 +16,8 @@
8. 接收卡片按钮回调。
9. 支持接收人反馈已收到、处理中、已完成、有问题。
10. 记录飞书事件、通知发送结果和回调处理结果。
11. 旧卡片、失效卡片和重复点击必须幂等处理。
12. 平台内“有问题反馈”页需要在飞书手机端最小可用。
## 3. 第一版不做
@@ -59,7 +61,9 @@
-> 验签
-> 记录 feishu_events
-> 查找 notification / draft / task / reminder
-> 校验 event_id 或 idempotency_key
-> 校验操作人权限
-> 校验通知、草稿和卡片仍有效
-> 执行业务动作
-> 写 operation_logs
-> 返回飞书处理结果
@@ -80,7 +84,7 @@
9. `failure_records`
10. `operation_logs`
字段以 `docs/contracts/数据对象约定.md` 为准。
字段以 `docs/contracts/数据对象约定.md` 为准;权限、安全、日志脱敏、验签和幂等以 `docs/contracts/安全权限日志约定.md` 为准。
## 6. 接口需求
@@ -108,11 +112,13 @@
状态以 `docs/contracts/状态流转约定.md` 为准。幂等约束:
1. 同一个飞书 `event_id` 只处理一次。
1. 同一个飞书 `event_id` 只处理一次;没有稳定 `event_id` 时必须使用 `idempotency_key`
2. 同一个通知只允许一个有效发送结果。
3. 同一个卡片按钮重复点击不得重复创建事项、提醒或反馈。
4. 同一个机器人消息还必须按 `source + message_id` 在老板秘书入口二次幂等,不重复追加 `secretary_messages`
5. 回调处理失败必须标记 `feishu_events.process_status = failed` 并写失败记录
4. 草稿确认类卡片必须匹配 `ai_drafts.active_card_notification_id`
5. 旧卡片、失效卡片、已替代草稿和已过期通知的回调只写入 `feishu_events`,标记 `ignored`,不得写业务对象
6. 同一个机器人消息还必须按 `source + message_id` 在老板秘书入口二次幂等,不重复追加 `secretary_messages`
7. 回调处理失败必须标记 `feishu_events.process_status = failed` 并写失败记录。
## 9. 失败和日志
@@ -128,9 +134,19 @@
8. 操作人无权操作。
9. 有问题反馈缺少原因。
普通日志不得打印飞书 token、App Secret、完整手机号或完整邮箱。
普通日志不得打印飞书 token、App Secret、OAuth code/state、一次性操作 token、回调验签密钥、完整手机号或完整邮箱。
## 10. 给 AI 的测试样例
## 10. 平台内反馈页
飞书卡片无法直接完成“有问题”原因填写时,允许跳转平台内反馈页。第一版不做完整手机网页适配,但该页在飞书手机端必须最小可用:
1. 能通过飞书登录态或一次性反馈 token 识别反馈人。
2. 一次性反馈 token 只保存 hash,不保存明文。
3. 只展示事项或提醒摘要、反馈状态选择和问题原因输入。
4. 操作人不是接收人、链接过期、通知失效或卡片失效时,只提示不可反馈并记录事件或失败,不写业务对象。
5. 提交成功后写入 `feedbacks` 并按状态流转更新事项或提醒。
## 11. 给 AI 的测试样例
1. 验签失败的回调应返回拒绝并写失败记录。
2. 同一 `event_id` 重放应幂等,不重复创建反馈。
@@ -138,11 +154,15 @@
4. 老板点击取消草稿后,草稿不能再转换。
5. 非老板私聊机器人不应调用 AI。
6. 飞书通知失败时,应写 `failure_records` 并保留通知失败状态。
7. 没有稳定 `event_id` 的卡片回调应使用 `idempotency_key` 幂等。
8. 旧确认卡片或已失效通知的回调只记录 `feishu_events`,不得创建事项、提醒或反馈。
9. 飞书手机端平台反馈页必须能提交“有问题”原因,并拒绝过期 token。
## 11. Review 重点
## 12. Review 重点
1. 回调是否验签。
2. 回调是否幂等。
2. 回调是否使用 `event_id``idempotency_key` 幂等。
3. 飞书身份是否没有直接绕过平台权限。
4. 通知内容是否只发摘要和链接。
5. 失败是否可复盘。
6. 旧卡片、失效卡片是否不会继续写业务对象。
+4 -2
View File
@@ -9,7 +9,7 @@
1. 支持一次性提醒。
2. 支持每天、每周、每月固定周期提醒。
3. 支持提醒来自手动创建。
4. 支持提醒来自老板 AI 草稿 `CONFIRMED` 后转换。
4. 支持提醒来自老板 AI 草稿 `confirmed` 后转换。
5. 支持提醒与事项关联。
6. 到点后通知接收人。
7. 通知成功或失败都要记录。
@@ -44,6 +44,7 @@
```text
scheduler 扫描 due reminders
-> 校验提醒状态
-> 用事务锁定当前批次
-> 生成幂等键
-> 创建 notifications
-> 调用飞书发送
@@ -107,7 +108,7 @@ scheduler 扫描 due reminders
2. `paused` 不触发,但可以恢复。
3. `cancelled` 不触发、不可恢复。
4. `trigger_failed` 必须写失败记录。
5. 同一个提醒同一触发时间同一接收人只能生成一条有效通知。
5. 同一个提醒同一触发时间同一接收人同一渠道只能生成一条有效通知。
6. 一次性提醒成功触发后进入 `triggered`;周期提醒成功触发后保持 `active` 并计算下一次 `next_trigger_at`
7. 提醒反馈不改变提醒调度状态,反馈结果通过 `feedbacks` 展示。
8. AI 提醒草稿缺少明确时间时不得创建 active 提醒,应由 01 模块追问或标记 `missing_fields`
@@ -139,3 +140,4 @@ scheduler 扫描 due reminders
3. 是否不支持 cron 等第一版不做内容。
4. scheduler 是否走 service 层而不是直接改业务表。
5. 失败是否可复盘。
6. 是否使用事务锁和 `notifications.idempotency_key` 唯一约束防止重复触发。
@@ -4,6 +4,8 @@
权限、日志和失败记录模块负责保证第一版闭环可控、可追溯、可复盘。权限必须在后端校验,关键操作必须写日志,AI、飞书、调度和业务失败必须形成失败记录。
权限、安全、日志脱敏、飞书验签幂等和访问边界的全局事实源是 `docs/contracts/安全权限日志约定.md`;本 spec 只说明模块目标和业务补充。
## 2. 第一版做什么
1. 定义老板、程经理、普通员工、管理员 / AI 团队四类角色。
@@ -68,6 +70,8 @@
字段以 `docs/contracts/数据对象约定.md` 为准。
安全和访问边界以 `docs/contracts/安全权限日志约定.md` 为准。
## 6. 接口需求
主要接口:
@@ -92,6 +96,7 @@
5. 普通员工不能给别人创建事项或提醒。
6. 反馈人必须是事项或提醒接收人。
7. 飞书登录用户必须匹配到启用状态的平台用户后才能进入平台。
8. 模型调用日志、飞书原始 payload、失败记录原始错误和一次性操作 token 明文不得对普通员工开放。
## 8. 状态流转
@@ -129,6 +134,8 @@
5. 日志中不得包含完整手机号、完整邮箱、飞书 token 或密钥。
6. 未匹配平台用户的飞书登录应失败并写失败记录。
7. PostgreSQL AI 记忆读写失败时,应写 `memory_store_failed`,不得继续依赖内存确认草稿。
8. 飞书旧卡片或失效卡片回调只记录事件,不写业务对象。
9. 一次性操作 token 只能保存 hash,普通日志不得出现明文。
## 11. Review 重点
@@ -137,3 +144,4 @@
3. 失败记录是否包含关联对象、失败类型和处理结果。
4. 日志是否记录操作人和渠道。
5. 敏感信息是否被脱敏。
6. 访问范围是否符合 `安全权限日志约定.md`
+5
View File
@@ -0,0 +1,5 @@
# tests
`tests/` 是真实约束目录。Django 项目骨架落地前,本目录只做占位;后续测试代码按 Django 默认测试或 pytest 约定放入这里或各 app 的 `tests/` 中。
每个 active plan 完成前必须说明新增或修改了哪些测试、覆盖了哪些 spec / contract 约定、如何运行,以及还有哪些关键风险未覆盖。