feat(feishu): 添加飞书入站事件inbox和混合数据库协调功能 - 实现飞书入站事件持久化inbox机制,支持状态管理、租约锁定和重试退避 - 添加混合数据库基线协调工具,确保平台PostgreSQL结构安全对齐 - 增加运行组件心跳检测和readiness就绪检查机制 - 实现app_ticket事件的安全轮换和验证处理 - 添加生产环境运行编排和fail-closed安全机制 - 支持webhook快速确认和长连接独立进程处理 - 完善个人数据擦除时的待处理事件清理功能 ```
16 KiB
需求文档
简介
本功能为飞书用户提供基于飞书账号身份的安全问答、个人规则、长期记忆、 兴趣偏好和定时推送能力。飞书账号是终端用户权限的唯一来源;内部 API Key 继续用于服务间认证,不得代表或伪造终端用户。
本功能只允许写入平台自身的身份、权限、偏好、订阅、记忆、审计和投递状态。 现有业务 MySQL 必须保持 SELECT-only,不得增加业务数据回写、审批、付款、 绩效定级、交易或其他越权动作。
已确认边界
- 单个飞书应用可以服务一个或多个飞书租户。
- 飞书用户身份使用
tenant_key + open_id唯一确定。 union_id和飞书user_id可以作为关联标识,但不得单独作为默认权限主键。chat_id只表示会话或群推送目标,不表示用户身份。- 个人数据默认不共享;公司级规则必须经过管理员权限控制。
- 用户规则和兴趣属于低信任数据,不能覆盖系统安全、只读、权限和审计规则。
- 本阶段不建设终端用户 Web 页面,不同步修改飞书通讯录,也不回写任何业务系统。
需求列表
需求 1:飞书账号身份认证
用户故事: 作为飞书用户,我希望系统准确识别我的飞书账号,以便所有问答、 规则和订阅都归属于我本人。
验收条件
- WHEN 系统收到已经通过飞书验证的 webhook 或长连接消息事件 THEN 系统 SHALL
从事件中提取
tenant_key和发送者open_id,并解析为唯一用户身份。 - IF 消息事件缺少
tenant_key、open_id或未通过飞书来源验证 THEN 系统 SHALL 拒绝执行用户命令,且不得创建任何个人数据。 - WHEN 一个合法飞书账号首次与机器人交互 THEN 系统 SHALL 为其建立默认普通用户身份, 且不得自动授予管理权限。
- WHERE 不同租户出现相同
open_idTHEN 系统 SHALL 将其识别为不同用户。 - WHEN 系统处理群聊消息 THEN 系统 SHALL 以消息发送者账号作为用户身份,而不是以
chat_id或群成员身份作为用户身份。 - IF 飞书用户被停用 THEN 系统 SHALL 拒绝其所有受保护命令并记录拒绝审计。
需求 2:基于飞书账号的角色与权限
用户故事: 作为管理员,我希望权限绑定到飞书账号,以便普通用户只能操作自己的数据, 而受信任人员才能使用公司级能力。
验收条件
- WHEN 新飞书用户首次注册 THEN 系统 SHALL 仅授予普通用户角色。
- WHEN 用户执行命令 THEN 系统 SHALL 根据该飞书身份的有效角色和权限决定是否允许执行。
- IF 普通用户尝试创建、修改或停用公司级规则 THEN 系统 SHALL 拒绝操作并记录审计。
- IF 用户尝试访问未授权的公司、项目、财务、风险或审计能力 THEN 系统 SHALL 返回不泄露受保护数据的拒绝响应。
- WHEN 内部服务管理用户角色或状态 THEN 系统 SHALL 要求有效的内部服务认证, 且角色变更 SHALL 记录操作者、目标飞书用户、变更前后状态和时间。
- IF 请求正文提供
actor、open_id或其他可伪造身份字段 THEN 系统 SHALL 忽略这些字段作为权限依据。
需求 3:个人规则和记忆隔离
用户故事: 作为飞书用户,我希望我的规则和历史记忆只影响我自己的回答, 以免其他用户看到或受其影响。
验收条件
- WHEN 用户创建规则、偏好或 AI 记忆 THEN 系统 SHALL 将记录关联到当前飞书用户所有者。
- WHEN 系统列出、召回、更新、启停或删除个人规则与记忆 THEN 系统 SHALL 强制按当前用户所有者过滤。
- IF 用户提交其他用户的数据编号 THEN 系统 SHALL 返回未找到或无权限, 且不得泄露该记录是否存在。
- WHEN 两个用户保存相同内容 THEN 系统 SHALL 分别保存或去重到各自所有者范围内, 不得复用另一用户的记录。
- WHEN 用户在群聊中创建个人规则或产生记忆 THEN 系统 SHALL 仍将其仅归属于发送者。
- WHERE 公司级规则被应用 THEN 系统 SHALL 仅使用已经启用且具有管理员来源的公司规则。
- WHEN 迁移现有无所有者的规则和记忆 THEN 系统 SHALL 不得把旧数据自动暴露给任意个人; 无法安全归属的数据 SHALL 进入停用或待审状态。
需求 4:个人规则、偏好和兴趣管理
用户故事: 作为飞书用户,我希望通过飞书告诉机器人我的规则、表达偏好和兴趣, 并能随时查看或撤销,以便后续交流符合我的要求。
验收条件
- WHEN 普通用户发送学习规则命令 THEN 系统 SHALL 默认创建个人规则,而不是公司规则。
- WHEN 有权限的管理员明确发送公司规则命令 THEN 系统 SHALL 创建公司级规则并记录审计。
- WHEN 用户设置语言、语气、内容详略、关注主题或其他偏好 THEN 系统 SHALL 以结构化个人偏好保存并关联当前用户。
- WHEN 用户添加或移除兴趣 THEN 系统 SHALL 更新当前用户的兴趣数据, 且现有个人股票自选 SHALL 作为市场兴趣来源之一。
- WHEN 普通问答中出现语言、语气、详略或内容主题等白名单偏好线索且真实 AI 提供方可用 THEN 系统 SHALL 静默提取并永久保存非敏感偏好;IF 内容涉及密钥、 健康、宗教、政治、性取向、绩效或财务秘密 THEN 系统 SHALL 拒绝保存。
- WHEN 用户请求查看、启停、修改或删除个人规则与偏好 THEN 系统 SHALL 仅操作当前用户的数据并返回明确结果。
- IF 用户规则与安全、权限、只读或公司规则冲突 THEN 系统 SHALL 忽略冲突部分并保留审计。
需求 5:个性化 AI 问答
用户故事: 作为飞书用户,我希望 AI 根据我的规则、偏好、兴趣和相关记忆回答, 同时不混入其他用户的信息。
验收条件
- WHEN 用户发起 AI 问答 THEN 系统 SHALL 按当前飞书身份加载公司规则、个人规则、 个人偏好、个人兴趣和当前用户的相关记忆。
- WHEN 系统组装 AI 上下文 THEN 系统 SHALL 按照“系统安全与只读规则、公司规则、 个人明确规则、当前请求、偏好与兴趣、相关记忆”的优先顺序处理。
- IF AI 提供方支持会话标识 THEN 系统 SHALL 为不同飞书用户使用隔离的会话标识, 不得复用全局用户会话。
- IF AI 提供方不可用或仍为占位提供方 THEN 系统 SHALL 返回明确的不可用响应, 不得把占位内容当作真实个性化答案。
- WHEN 问答来自群聊 THEN 系统 SHALL 回复原会话,但个人规则、偏好和记忆仍只属于发送者。
- IF 个人规则试图要求调用未授权工具、修改业务数据或绕过安全控制 THEN 系统 SHALL 拒绝执行该要求。
需求 6:个人推送订阅
用户故事: 作为飞书用户,我希望按自己的兴趣和时间订阅消息,并能暂停或退订, 以便只收到需要的内容。
验收条件
- WHEN 用户创建订阅 THEN 系统 SHALL 保存订阅主题、飞书接收身份、计划时间、时区、 安静时段、启用状态和用户同意时间。
- WHEN 用户未明确创建或同意订阅 THEN 系统 SHALL 不得主动向该用户发送个人推送。
- WHEN 用户查看订阅 THEN 系统 SHALL 仅返回当前用户的订阅。
- WHEN 用户暂停、恢复或退订 THEN 系统 SHALL 立即更新当前用户订阅状态并记录审计。
- IF 用户尝试修改其他用户订阅 THEN 系统 SHALL 拒绝且不泄露目标订阅信息。
- WHEN 订阅主题支持个人兴趣过滤 THEN 系统 SHALL 使用当前用户明确保存的兴趣生成内容。
- WHILE 当前时间位于用户安静时段内 THEN 系统 SHALL 延后非紧急推送,不得直接发送。
- IF 订阅时间、时区、主题或接收目标无效 THEN 系统 SHALL 拒绝创建并返回可操作的错误说明。
- WHEN 用户以受控中文时间语法创建订阅且解析成功 THEN 系统 SHALL 立即启用订阅, 返回标准化计划、下次执行时间和暂停命令。
- IF 订阅间隔短于 15 分钟、用户已有 50 个启用订阅或当日投递将超过 96 条 THEN 系统 SHALL 拒绝创建或跳过投递并说明限制。
- WHEN 普通用户创建订阅 THEN 系统 SHALL 固定绑定其本人
open_id; WHEN 管理员在群内创建群订阅 THEN 系统 SHALL 仅绑定当前事件的chat_id, 且 SHALL 拒绝用户手工提供原始chat_id。
需求 7:可靠的个性化定时投递
用户故事: 作为订阅用户,我希望消息按时且不重复地送达,并在临时失败后安全重试。
验收条件
- WHEN 个人订阅到期 THEN 系统 SHALL 通过持久化订阅状态发现任务并生成投递请求, 不得仅依赖进程内临时任务。
- WHEN 系统发送个人消息 THEN 系统 SHALL 默认使用当前用户的飞书
open_id作为接收目标。 - WHEN 系统处理同一用户、同一订阅和同一时间窗口 THEN 系统 SHALL 只创建一次有效投递。
- IF 飞书返回 HTTP 错误、限流响应或非零业务状态码 THEN 系统 SHALL 将投递标记为失败, 不得记录为成功。
- IF 投递失败属于可重试错误 THEN 系统 SHALL 按受限退避策略重试; 超过最大次数后 SHALL 进入最终失败状态。
- WHEN 投递成功 THEN 系统 SHALL 保存飞书响应标识、发送时间和最终状态。
- IF 多个调度器或工作进程同时扫描同一订阅 THEN 系统 SHALL 防止重复生成或重复发送消息。
- WHILE
READ_ONLY_MODE=trueTHEN 系统 SHALL 允许平台自身的订阅与投递状态写入, 但 SHALL 保持旧业务数据库只读。
需求 8:飞书自助命令
用户故事: 作为飞书用户,我希望直接通过机器人管理个人规则、偏好和订阅, 无需访问后台页面。
验收条件
- WHEN 用户请求帮助 THEN 系统 SHALL 返回其当前权限允许使用的命令说明。
- WHEN 用户管理个人规则 THEN 系统 SHALL 支持创建、查看、修改、启停和删除。
- WHEN 用户管理偏好或兴趣 THEN 系统 SHALL 支持设置、查看和删除。
- WHEN 用户管理订阅 THEN 系统 SHALL 支持创建、查看、暂停、恢复和退订。
- WHEN 管理员执行公司级命令 THEN 系统 SHALL 在执行前验证其飞书账号权限。
- IF 命令格式错误 THEN 系统 SHALL 返回示例格式,且不得产生部分写入。
- IF 命令涉及敏感信息、密钥或禁止内容 THEN 系统 SHALL 拒绝保存并给出安全提示。
- WHEN 管理员通过飞书管理用户角色或状态 THEN 系统 SHALL 只接受当前验证事件中的
结构化
@用户身份,不得接受文本伪造的open_id。 - IF 操作会停用或降级最后一个有效管理员 THEN 系统 SHALL 拒绝操作。
需求 9:隐私、审计和用户控制
用户故事: 作为飞书用户和审计人员,我希望个性化数据受到保护且关键操作可追踪。
验收条件
- WHEN 系统认证用户、拒绝权限、修改规则、修改偏好、修改订阅或执行投递 THEN 系统 SHALL 记录必要的审计元数据。
- WHEN 系统记录审计或错误 THEN 系统 SHALL 避免保存访问令牌、密钥和不必要的完整个人问答内容。
- WHEN 用户请求删除个人画像和记忆 THEN 系统 SHALL 删除或匿名化可删除的个人数据, 停止其订阅,并保留满足审计要求的最小不可逆证据。
- WHEN 用户请求查看自己的个性化数据 THEN 系统 SHALL 返回其规则、偏好、兴趣和订阅摘要。
- IF 内部服务查询个人数据 THEN 系统 SHALL 根据服务权限限制数据范围并记录访问审计。
- WHEN 用户发送“忘记我” THEN 系统 SHALL 返回短时有效的一次性确认码; WHEN 同一用户发送“确认忘记我 <码>”且验证码有效 THEN 系统 SHALL 在一个事务中 删除其个人数据、停用订阅、移除身份映射并将审计主体替换为随机匿名标识。
需求 10:兼容性、迁移与验证
用户故事: 作为维护人员,我希望现有固定群推送和内部 API 保持可用, 同时安全升级到飞书用户权限体系。
验收条件
- WHEN 数据库升级 THEN 系统 SHALL 通过 Alembic 创建身份、权限、偏好、订阅和投递所需结构。
- WHEN 升级现有数据库 THEN 系统 SHALL 保留已有审计、报表推送和业务只读数据。
- WHEN 现有固定群定时任务运行 THEN 系统 SHALL 保持原有功能,且不得被误认为个人订阅。
- WHEN 内部 API 使用 API Key 调用 THEN 系统 SHALL 保持服务级认证, 但不得通过请求参数伪造飞书终端用户权限。
- WHEN 自动化测试运行 THEN 系统 SHALL 覆盖两个不同飞书用户的数据隔离、 越权拒绝、角色权限、个人问答上下文、订阅生命周期、安静时段、幂等投递、 飞书非零业务码和数据库迁移一致性。
- WHEN 完整验证运行 THEN Ruff、Python 编译检查、Alembic 元数据一致性测试和全部 pytest 测试 SHALL 通过。
需求 11:混合数据库安全基线
用户故事: 作为维护人员,我希望现有未登记 Alembic 版本的混合平台数据库可以安全 协调到当前模型,而不丢失已有平台数据或误操作业务数据库。
验收条件
- WHEN 协调工具连接数据库 THEN 系统 SHALL 只接受平台 PostgreSQL,且不得连接或修改
LEGACY_DATABASE_URL。 - WHEN 数据库版本为空且结构符合已审核的混合基线 THEN 系统 SHALL 在事务锁内仅执行 允许列表中的结构补齐、旧约束替换和已确认空表清理。
- IF 实际结构指纹、数据行数、依赖关系、权限或 Alembic 版本不符合预检 THEN 系统 SHALL 中止且不得留下部分 DDL 或版本记录。
- WHEN 协调完成 THEN 系统 SHALL 验证 SQLAlchemy metadata 无漂移,再原子登记 Alembic 版本;IF 任一步失败 THEN 全部变更 SHALL 回滚。
- WHEN 常规数据库已经位于受支持 revision THEN 系统 SHALL 继续使用标准 Alembic 升级, 不得重复执行一次性基线协调。
需求 12:生产运行与就绪判定
用户故事: 作为运维人员,我希望 API、飞书事件、调度器和任务执行进程可以持续运行, 且系统只在依赖和迁移真正就绪时接收流量。
验收条件
- WHEN 启用飞书用户功能 THEN 系统 SHALL 明确选择
webhook或long_connection事件入口; IF 对应凭据或进程未就绪 THEN production 配置或 readiness SHALL 失败关闭。 - WHEN 使用长连接部署 THEN 运行编排 SHALL 启动独立飞书事件进程,并为 API、事件进程、 scheduler 和 worker 配置重启策略。
- WHEN scheduler、worker 或长连接属于当前配置期望组件 THEN 系统 SHALL 要求其存在新鲜 heartbeat;缺失或过期 SHALL 使 readiness 返回 HTTP 503。
- WHEN readiness 检查平台数据库 THEN 系统 SHALL 验证当前 Alembic revision 与 head 一致,
而不只执行
SELECT 1。 - WHEN 生成部署配置样例 THEN 系统 SHALL 只包含占位符和安全默认值,不得提交真实密钥; 外部 PostgreSQL 部署 SHALL 能避免被 Compose 内部数据库 URL 强制覆盖。
需求 13:可靠的飞书入站事件
用户故事: 作为飞书用户,我希望机器人在临时故障或进程重启后仍能处理我的消息, 同时不会因飞书重复投递而重复执行命令。
验收条件
- WHEN webhook 或长连接收到已经验证的事件 THEN 系统 SHALL 先以唯一事件键持久化 inbox 状态,再快速确认接收,不得在 webhook 请求内同步等待 AI 或飞书回复。
- IF 多个进程并发接收相同事件 THEN 系统 SHALL 只创建一个 inbox 记录,并只允许一个 有效租约执行命令。
- IF 命令执行发生可重试失败或执行进程在完成前退出 THEN 事件 SHALL 回到持久化重试状态, 不得因为 receipt 已登记而永久丢失。
- WHEN 命令执行成功 THEN 系统 SHALL 原子标记成功;后续重复事件 SHALL 返回已接收且不得 再次执行命令。
- WHEN 重试超过上限 THEN 系统 SHALL 标记最终失败、保存最小错误摘要并提供运行指标, 不得记录密钥或完整敏感消息内容。