新增 Business Analysis Agent 设计稿,明确 Semantic Layer、Metric Catalog、Analysis Strategy、Analysis Plan Processor、Metric Engine、统一 ChartSpec、Insight/Report/Evidence/Follow-up 和 Apple Vision 图表规范。 同步记录关键架构决策,约束 AI 只提出分析计划建议,系统校验规范化后执行,且不得绕过权限或直接生成 SQL。 Co-Authored-By: GPT-5 Codex <codex@openai.com>
61 KiB
关键设计决策记录
每条决策都包含为什么这么做,避免后续模型重新讨论或推翻。
1. 进度计算 — 由状态推导,不要手填百分比
问题:Jira/TAPD 等系统让开发手填进度(20%/35%/50%),数据完全主观,不可靠。
决策:DevTask 进度由 status 推导:
todo→ 0%in_progress→ 50%testing→ 80%submitted→ 100%
理由:客观、不允许造假。需求级/版本级进度 = Σ(估算耗时 × 状态推导%) / Σ(估算耗时)。估算耗时优先取执行人填写的 estimateHours,没有时才用 AI 草案的 aiEstimateHours 兜底。
2. 实际工时 — 由开始/完成时间戳计算
问题:让人手填工时太粗糙(一天 8h 真的全部花在这个任务吗?)。
决策:精确到 0.5h 的时间差计算
实际工时 = (completedAt - startDate) / 3600000,精确到 0.5h
理由:状态流转时自动记录精确时间戳,工时直接派生。
3. DevTask "已提测" = 终态
问题:原本有 submitted → done,但开发提测之后就交付完成了,后续测试结果不应该回过来改 DevTask 状态。
决策:去掉 done,submitted 是终态。测试通过/失败产生 Bug,不影响 DevTask。
理由:DevTask 的责任是"开发到提测",验收是 TestCase 的职责。
4. 调研/产品/UI — 自动开始 + 手动提前开始
问题:到了计划开始日期,状态还是 pending 不合理。
决策:
- 当前时间 ≥
startTime时自动视为 in_progress - 用户可在计划日期前手动点"开始"
- 自动开始时记录
actualStartAt(精确到分钟)
理由:减少冗余操作但保留灵活性。
5. TestCase 主归属版本,需求是可选标签
问题:早期把测试用例挂在需求上,但同一需求跨版本迭代时用例难以管理。
决策:
- TestCase.versionId 必填(执行归属)
- TestCase.requirementId 可选(语义标签)
- 测试人员从"我的测试用例"看到的就是当前版本的用例
理由:测试是按版本执行的,不是按需求;但需求作为思考维度仍然有用。
6. Bug 直接挂版本
问题:早期通过 Bug → TestCase → Requirement → Version 四级反查,太绕。
决策:Bug 增加 versionId 字段,直接挂版本。testCaseId 保留作为来源追溯。
理由:判断"能不能发布"的唯一维度是版本,必须 O(1) 查询。
7. 跨模块通信 — 引擎层,不是事件总线
问题:模块多了,A store 改动影响 B 的派生数据,散落各处难维护。
决策:建立纯函数引擎:
linkage-engine.ts:从 DevTask 状态派生需求"实际进度"workspace-engine.ts:聚合所有 store 数据为统一 WorkItem
理由:事件总线过度设计,纯函数派生更可预测。新增模块只需在引擎中添加聚合逻辑。
8. 时间字段统一 ISO 时间戳到分钟
问题:日期(YYYY-MM-DD) vs 时间戳混用,UI 展示不一致。
决策:所有"实际开始/完成"用 new Date().toISOString(),展示统一 slice(0, 16).replace('T', ' ')。
理由:时间戳到分钟够精确,不像秒/毫秒那么噪。
9. 任务类型字典化 + 分组
问题:早期 enum 写死类型(前端/后端/大数据/其他),新增类型需改代码。
决策:建 TaskCategory 字典表 + group(development/implementation/other),管理员可增删。
理由:业务变化快,配置 > 写死。group 方便统计(开发工时 vs 实施工时)。
10. 阻塞正交标记,不是状态值
问题:status: blocked 会丢信息——开发中+阻塞 ≠ 自测+阻塞。
决策:
status: todo|in_progress|testing|submittedisBlocked: boolean+blockReason: string+blockedById: string- DevTask 存在阻塞时不能转为
submitted,必须先解除阻塞再提测。
理由:阻塞和状态正交。工作台筛选 isBlocked=true 一键拉出所有阻塞项,跨状态;但 submitted 代表开发交付完成,仍必须满足“当前无阻塞”的完成条件。
11. 加班原因可选,且去掉"其他"
问题:可选"其他"会让人逃避思考具体原因。
决策:变更原因/加班原因严格定义有限选项,没有"其他"。
理由:强制具体化,统计才有意义。
12. 项目维度 vs 个人维度耗时分开计算
问题:多个开发并行做任务,简单加总会重复计算时间。
决策:
- 个人耗时:每个任务独立累加(
Σ(end - start)) - 项目维度阶段耗时:
min(start)→max(end)日历跨度(不重复)
理由:两个维度回答不同问题——"谁花了多少时间" vs "这个阶段拖了多久"。
13. 删除版本时清理孤儿数据
问题:删版本只删 version 实体,留下 PlanTask/DevTask/TestCase/Bug 变成孤儿数据。
决策:删除版本时级联清理:
- 释放需求 versionId(回需求池)
- 删除 PlanTask
- 删除 DevTask(通过需求 ID)
- 删除 TestCase
- 删除 Bug
理由:localStorage 没外键约束,必须手动级联。
14. 工作台数据点击 → 侧边详情 + 可操作
问题:早期工作台只显示信息,操作要跳转到版本详情。
决策:点击卡片打开 Drawer(复用版本详情用的同一组件),可在 Drawer 内完成状态流转、转交、提 Bug 等。
理由:减少跳转,工作台一站式处理。Drawer 顶部显示"产品/项目/版本"上下文,不会迷失。
15. UI 阴影统一 shadow-2xl
问题:各 Drawer 阴影不一致(有些 shadow-lg 有些 shadow-2xl)。
决策:全局 Drawer 用 shadow-2xl。
理由:视觉权重明显,且一致。
16. 不进入 Brainstorm 模式的判定
约定:以下情况直接修复,不强制走 brainstorming 流程:
- Bug fix(明确 bug,方案直接)
- 已批准方案的 in-progress 延续
- 用户已通过 AskUserQuestion 选择了方案
- 简单文案/样式调整
理由:brainstorming 适合新功能设计,bug fix 强行套流程浪费时间。
17. AI 拆解不存历史原型,靠对账报告兜底
问题:FTB 接入时,禅道里项目可能已经迭代到 V1.6,前面历史原型不在系统里。AI 拆解 V1.6 时如果做"V1.5 → V1.6 diff",需要用户额外提供历史原型 URL。
决策:不要求用户提供历史原型 URL。AI 拆解只用三样输入:当前版本原型 + 关联需求 + 版本成员。
对应风险:AI 可能把"上版本就有的功能"也当成"本次新做"。
应对:要求 Agent 输出对账报告,结构化呈现:
- ✅ 完美对应(需求 ↔ 原型注释 ↔ 任务)
- ⚠️ 需求未见原型
- 无需求ID分组(原型有明确功能但没有匹配到关联需求)
- ❓ 注释含糊无法转化
理由:让历史版本入库的成本远高于让用户对账的成本。对账报告本来就是 AI 拆解的标配,能同时兜住"拆偏"和"拆漏"两类问题。让团队补需求清单,比让团队补历史原型容易。
18. AI 写入必须带 aiDraft 标记 + 引用来源
问题:AI 直接写入开发任务/用例,团队无法分辨"这是 AI 拆的还是人手填的",出错时找不到来源。
决策:
- DevTask / TestCase 加
aiDraft: boolean字段 - AI 写入时强制
aiDraft: true - 列表中 AI 草案视觉区分(紫色左边线 + AI 草案徽章)
- 用户编辑保存后
aiDraft自动清除(在 store 的 update 方法里实现) - 同时加
references: Reference[]字段,记录引用来源(需求 / 原型批注)
理由:
- 可识别性:团队一眼分辨 AI 产出 vs 人工产出
- 可追溯性:每条任务知道是从哪条需求 + 哪条原型批注来的
- 可信任:用户编辑即认可,自动转正常状态
19. 引用机制对 AI 和人工一致
问题:如果只有 AI 任务带 references,人工任务不带,会出现两套规则:用户看到 AI 任务有引用、人工没有,体验割裂;做"任务可追溯"功能时数据缺失。
决策:人工创建 DevTask / TestCase 时也强制带 references。
- 表单加「关联需求」(从版本已加需求里多选,自动转 requirement 类型 reference)
- 表单加「原型批注」(手填编号或自由文本,逗号/空格分隔,自动转 prototype_note 类型 reference)
- AI 草案唯一区别只剩
aiDraft: true这一个状态字段
理由:单一数据形状,UI 复用;可追溯性是任务本身的属性,不该由"谁创建"决定。
20. AI 配置走页面不走 .env
问题:早期 ANTHROPIC_API_KEY 只能写到 apps/server/.env,配置流程对非技术人员不友好;切换 key 要重启 NestJS。
决策:
- 加
apps/server/src/modules/config/模块,提供 GET/PATCH/POST/api/v1/config/ai接口 - 配置存到
apps/server/data/ai-config.json(在 .gitignore 里) - 前端
/admin/ai-config页面(仅超管可见)支持修改 Key、切换模型、测试连接 - AiGateway 优先读文件,其次读环境变量;切换 key 后客户端单例自动重建
- 前端 GET 接口永远不返回完整 Key,只返回 mask(如
sk-ant...xxxx)
理由:
- 运营/管理可以自助维护,不依赖工程师改代码
- Key 在服务器存储,前端永远拿不到完整内容,安全等同 .env
- 走 .env 的兜底保留,给 CI / 早期部署留路径
21. 多 AI 提供商抽象,支持中转站
问题:早期 AiGateway 硬绑定 Anthropic 官方 SDK,无法走 ikuncode 等中转站,也不能切换到 OpenAI 格式提供商。
决策:引入 Provider 抽象层。
apps/server/src/modules/ai/providers/下两个实现:AnthropicProvider/OpenAIProvider- 共同实现
AiProvider接口(callTool/ping),把厂商差异封装在 provider 内部 prompts/decompose.ts用格式无关的 JSON Schema,由各 provider 包装成 Anthropic 的input_schema或 OpenAI 的parameters- 配置数据结构升级为
{ providers: [], activeProviderId },支持配置多个 provider 运行时切换 - 旧的单 key 配置自动迁移成
providers[0]
支持范围:当前两类格式覆盖 99% 中转站
anthropic:Anthropic 官方 / 中转站(兼容 Messages API + tool_use)openai:OpenAI 官方 / 中转站(兼容 Chat Completions + tool calling)
理由:
- 用户可以自由选择厂家或中转站,不被一家锁死
- 每个 provider 独立 model 字段,因为不同提供商上同名模型不一定可用
- "激活"概念让多套配置同时存在,方便 A/B 切换
- 抽象层让上层 AiService 完全不感知厂商差异,未来加 Risk Watch / Schedule Suggest Agent 时复用
22. 业务数据先迁到 PostgreSQL AppData,再逐步关系化
问题:V1 业务数据存在浏览器 localStorage 中,用户在 Application Storage 执行 Clear site data 后,产品、项目、版本、需求池、版本详情里的调研/产品方案/UI/开发任务/测试用例/Bug,以及成员、任务类型等都会丢失。系统未来要部署到服务器,浏览器不能作为业务数据主存储。
决策:先引入服务端通用数据文档层:
- Prisma 增加
AppData模型(表app_data,key+ JSONBvalue) - NestJS 增加
DataModule,通过GET/PUT /api/v1/data/:key读写允许的业务数据 key - 前端 store 保持现有数据形状和业务逻辑,只把持久化从 localStorage 切换到
/data/:key - 覆盖 key:
products-overview、requirements、version-plans、dev-tasks、test-cases、bugs、members、task-categories、task-worklogs、overtime - 浏览器只保留登录态,不保存业务主数据
理由:
- 当前模块多、数据形状仍在快速调整,一次性全量关系化风险高、改动面大
- JSONB 文档表能立刻解决“清浏览器数据导致业务数据丢失”的线上部署问题
- 保持 store 数据形状不变,能降低迁移对现有 UI、AI 拆解、工作台聚合的冲击
- 后续等字段稳定后,再把
app_data中的文档逐步迁入关系表和领域 CRUD API
23. 版本计划完成条件进入工作流引擎,任务类型使用稳定语义码
问题:版本模块里,调研/产品方案/UI 设计的完成动作散在 PlanTab 和 PlanDetailDrawer,右上角勾选按钮会绕过子任务和成果提交。开发任务的任务类型太粗,测试用例没有任务类型,AI 拆解只输出 role,无法稳定映射到业务分类。
决策:
- 新增
version-plan-workflow.ts,统一判断计划是否可勾选子任务、是否可提交成果、是否可完成、缺少什么条件。 - 取消计划卡片右上角的“直接完成”勾选按钮,完成只能通过提交成果触发。
- 调研/产品方案/UI 设计都使用子任务清单;完成时必须满足规则引擎要求并提交链接或文件成果。
- 新增
requirement-selector.ts,所有关联需求候选都从当前版本所属项目下的已采纳需求中取,不从全量需求池取。 - DevTask 和 TestCase 共用
TaskCategory字典,TestCase增加categoryId。 TaskCategory增加稳定code,早期用于 AIcategoryCode到系统categoryId的映射;后续 AI 任务类型改为以taskTypeName为主,见决策 #41。
理由:
- 完成条件属于业务规则,不属于按钮组件;集中到工作流引擎后,列表和抽屉能保持一致。
- 需求候选来源属于领域边界;统一 selector 能避免从需求池、项目需求、版本需求之间误取数据。
- AI 不应该猜数据库 id;稳定语义码能兼容管理员调整展示名称,也方便后续扩展更多模型。
- 测试用例带任务类型后,才能知道测试覆盖范围,AI 拆解和人工创建的数据形状也一致。
24. AI 预估和执行人预估分离
问题:AI 拆解写入 estimateHours 会让系统误以为负责人已经确认了工时和排期;同时 AI 会根据原型自动反推预计开始/截止,造成“计划已填”的错觉。
决策:
- AI 输出字段统一改为
aiEstimateHours,不再输出/写入estimateHours。 estimateHours只表示执行人或负责人确认后的执行预估。- 进度和统计口径优先使用
estimateHours;缺失时使用aiEstimateHours兜底;两者都没有时才从计划时间推导或按无估时处理。 - AI 采纳开发任务时不写预计开始/截止时间,由负责人后续填写计划排期。
- AI 重新拆解入口拆成“重新拆解开发任务”和“重新拆解测试用例”,请求通过
target控制本次只生成哪类草案。 - AI 估时允许小于 0.5h,汇总时保留小数精度;实际耗时仍按状态时间戳计算并保持 0.5h 口径。
理由:
- AI 给的是建议,不是负责人承诺。
- 排期属于执行人计划,不能由拆解 Agent 代填。
- 独立重拆能避免修改开发任务时顺手覆盖测试用例,降低误采纳风险。
25. AI 重新拆解先过滤已采纳重复草案
问题:第一次 AI 拆解的 DevTask/TestCase 已经被采纳后,用户再次重新拆解时,模型可能再次输出同一批草案。若直接展示给用户采纳,会造成开发任务和测试用例重复。
决策:
- AI 返回结果后,前端在打开采纳弹窗前先做确定性去重。
- 开发任务比对范围是当前版本已关联需求下的现有 DevTask;测试用例比对范围是当前版本下的现有 TestCase。
- 重复判定使用:任务类型 code + 标题规范化 + 引用来源(需求 / 原型批注)一致。
- 重复草案不进入采纳列表,只在弹窗提示已过滤数量。
理由:
- 去重不能只依赖模型提示,必须有系统规则兜底。
- 标题也参与签名,避免同一需求/QY 下不同真实工作项被误过滤。
26. 系统内置超级管理员账号不可删除
问题:全新系统需要一个稳定可登录的超级管理员账号。早期默认成员曾以普通姓名展示,用户手动改名后,登录态和 Bug 单据中的人员姓名可能仍保留旧快照,造成“提交人是旧姓名、修复人是新姓名”的错位。
决策:
- 固定
m-8为系统内置超级管理员账号,姓名、部门、角色由系统保护,不允许删除。 - 读取历史成员数据时自动迁移旧默认账号为“超级管理员”,保留手机号、邮箱、密码。
- 登录态以成员
id为稳定身份,页面启动和编辑当前用户后都按成员表刷新姓名、角色和联系方式。 - Bug 展示和筛选对历史旧名做兼容解析,旧单据中的默认账号旧姓名显示为当前超级管理员。
理由:
- 账号身份不能依赖可编辑姓名;姓名只是展示字段。
- 内置超管账号避免新系统初始化后被误删或降权导致无法管理。
- 历史单据兼容能修复已有数据的显示错位,同时不需要批量改写业务记录。
27. 测试轮次以第一轮用例为母版
问题:同一版本测试失败或修复后,经常需要重新跑一遍同一批测试用例。如果直接复用原用例,会覆盖第一轮执行记录;如果手工重建,又容易漏掉 AI 生成或人工补充的用例。
决策:
- TestCase 增加
roundNo,旧数据和未显式写入的数据统一视为第 1 轮。 - 点击“开启新一轮测试”时,只从第 1 轮复制用例到下一轮,AI 创建和人工创建的用例都复制。
- 只有最新一轮测试用例全部进入
passed/failed/blocked后,才能开启下一轮;第 1 轮未测完时不能开启第 2 轮。 - 复制时保留需求、标题、描述、类型、优先级、负责人、引用来源、AI 草稿标记、AI 预估和执行预估。
- 复制时清空执行状态、开始/完成时间、执行人、失败原因和阻塞原因,新轮次从
pending开始。 - 测试进度、无 Bug 通过率和列表按当前轮次查看;顶部 AI 预估、执行预估、实际耗时、人力投入按版本全部轮次累计。
- 测试用例按所属需求分组时展示“已提测/待提测”标签;只有该需求下所有开发任务都
submitted才展示“已提测”,否则展示“待提测”。
理由:第一轮承载完整测试范围,后续轮次应复跑同一范围而不是临时拼装;执行记录按轮次隔离,整体投入按版本累计,能同时回答“这一轮测得怎么样”和“这个版本测试总共花了多少”。
28. Daily report uses work activity log, not a generic rules engine
Problem: A daily report based only on manual task-worklogs misses important actions such as submitting a product plan, starting a development task, submitting code to test, fixing bugs, or marking blockers.
Decision: Add a lightweight work-activities document key and a typed work-activity-factory.ts. Business stores append activity records after successful operations. The workspace daily report derives a personal report from activities, legacy worklogs, and current work items.
Why:
- Automatic activity records provide evidence that work happened today.
- Manual progress notes explain multi-day work when no status changed today.
- A generic rules engine is too heavy for the current AppData stage and would hide business rules behind configuration.
- A pure aggregation function keeps report behavior testable and predictable.
Rule: Key status changes count as daily evidence. Multi-day in-progress work without today's activity or progress note is flagged as needing a progress update.
29. 计划日期使用中国节假日日历提示,不做硬阻断
问题:调研、产品方案、UI 设计、开发任务、测试用例和 Bug 修复都需要填写计划时间。原生日期控件样式不一致,也无法提示中国法定节假日和调休工作日。
决策:
- 新建统一的工作日日期时间选择组件,创建任务时复用同一套交互。
- 内置国务院办公厅发布的 2026 年中国法定节假日和调休工作日;未知年份按周末/工作日兜底。
- 选择节假日或周末时只提示,不阻止保存;选择调休工作日时按工作日提示。
- TestCase 增加
plannedTestAt/plannedEndAt,Bug 增加plannedFixAt,保存为 ISO 时间戳。
理由:项目排期需要贴近中国工作日,但研发和线上 Bug 可能确实安排在非工作日处理,所以系统负责提醒,最终是否保存交给用户判断。
30. 正常工时走中国工作日历,加班单独计入
问题:接入中国节假日后,如果任务从节假日前开始、节假日后结束,直接按自然时间差会把休息日误算为实际耗时。
决策:
calcWorkHours和正常任务的calcActualElapsedHours都按中国工作日历和工作时段计算。- 法定节假日、周末不计入正常工时;调休上班日计入正常工时。
- AI 预估工时是工作量建议,不按日期跳过;执行预估只有从计划起止时间自动推导时才走工作日历。
- 加班记录不受工作日历过滤,但不按自然小时连续打卡计算;使用项目管理口径:每个自然日默认 9:00-12:00、13:00-18:00 计 8h,中间完整日期按 8h,首尾日期按填写的开始/结束时间裁剪,结束晚于 18:00 的当天额外计入超出时长。
理由:正常任务耗时回答“工作时间里实际投入了多少”,加班记录回答“额外投入覆盖了多少项目工作量”。项目管理不做打卡,跨天记录不能把夜间空档算成工时;但节假日和周末仍允许记录,不受中国工作日历过滤。
31. AI 原型拆解优先按需求编号匹配,再按需求概述兜底
问题:原型文件里有些批注并不总是稳定写成 QY 编号,或者 QY 片段和需求之间没有显式绑定。仅按 QY 批注匹配会漏掉“原型里实际已有注释”的需求。
决策:
- 原型上下文提取不只围绕 QY 编号,也围绕当前版本关联需求的
id、code、title、description命中片段。 - Agent 对账优先按需求编号匹配;原型文本出现
requirement.id或requirement.code时必须优先归到该需求。 - 没有需求编号时,再按需求标题和需求概述做语义匹配。
- 没有 QY 编号但命中了需求编号或需求概述时,允许只引用
requirement,matched.noteIds返回空数组。 - 编号和概述都匹配不到、但能形成明确功能名称和任务范围的原型批注,进入
prototypeOnly无需求ID分组;只有无法稳定转化为任务/用例的批注才进入ambiguous。
理由:需求编号是最稳定的对账锚点,需求概述是编号缺失时的业务语义兜底。把匹配证据先送进上下文,再用 prompt 明确优先级,比只依赖模型从截断文本里自由联想更稳定。
32. 测试用例类型细分,AI 拆解按测试点而不是大流程输出
问题:测试用例原先只有功能/API/异常/兼容性四类,AI 容易把多个交互、接口、数据状态和边界场景合并成一条“大用例”,导致测试范围不够细。
决策:
- 在测试分组增加细分类:UI 交互、表单校验、数据一致性、权限、边界值、状态流转、回归测试。
- 早期 AI tool schema、shared 类型和前端任务类型字典同步允许这些
categoryCode;后续这些类型只作为建议映射,AI 可通过taskTypeName输出字典外类型,见决策 #41。 - Prompt 明确要求 TestCase 按功能点、交互、接口、数据、权限、异常、边界、状态流转和回归点拆细。
- 一条 QY 如果同时涉及 UI、接口、数据、异常和状态变化,通常应拆出 3-8 条 TestCase,不允许用单条“完整流程验证”兜住。
理由:测试用例的分类粒度会反过来影响 AI 输出粒度。更细的稳定语义码能让模型把测试范围拆开,也让后续统计、筛选和负责人评估更准确。
33. AI 可推荐负责人,但必须由用户确认后才写入
问题:版本成员已经维护完成后,AI 拆解任务时完全不看成员会浪费上下文;但如果 AI 直接分配负责人,容易把模型建议误认为团队承诺,也可能编造成员姓名造成脏数据。
决策:
- AI 草案允许输出
recommendedAssigneeName和recommendedAssigneeReason,但姓名只能来自当前版本成员members[].name。 - 前端用规则 helper 再校验推荐姓名是否属于当前版本成员,非成员姓名直接丢弃。
- 采纳弹窗默认不写入负责人;用户勾选“采纳推荐负责人”后,才把已校验推荐写入 DevTask/TestCase 的
assigneeId。 - 没有明确角色匹配或成员匹配时,AI 不输出推荐字段,任务保持未分配,供成员后续领取或手动分配。
理由:负责人推荐能减少项目经理初次分配成本,但分配本身是团队执行承诺,必须由人确认。把推荐和写入分开,可以复用版本成员上下文,又避免模型幻觉姓名或越权自动派单。
34. AI 草案领取和计划必须绑定
问题:AI 生成的 DevTask / TestCase 如果没有负责人,团队需要先领取;如果把“待领取”和“待排期”拆成两个可见状态,会让版本详情列表出现更多标签,且无法体现“领取时就应该承诺计划”的业务动作。
决策:
- 版本详情开发任务和测试用例不显示“待排期”标签。
- 无负责人时显示“待领取”,领取入口必须同时填写计划起止时间。
- 用户采纳 AI 推荐负责人后,草案已经有负责人,不再需要领取;但开始开发/测试前仍必须补齐计划起止时间。
- DevTask 进入
in_progress前必须具备assigneeId、expectedStartAt、expectedEndAt。 - TestCase 进入
running前必须具备assigneeId、plannedTestAt、plannedEndAt。
理由:领取代表成员承诺执行,计划时间代表承诺边界,二者应该在同一个动作里完成。列表层只表达“谁还没接手”,状态机层负责阻止未计划任务进入执行,页面不会被额外标签干扰。
35. 产品/UI 计划需求覆盖必须区分部分完成和完全完成
问题:产品方案和 UI 设计经常跨天推进,同一天可能只完成某条需求的一部分。旧的勾选式“引用需求已完成”只能表达完成/未完成,日报和后续分析无法知道本次完成了什么、还剩什么,也容易把部分完成误算成可提交成果。
决策:
- VersionPlan 增加
requirementCoverage[],每条引用需求记录not_started / partial / completed、已完成内容、剩余内容、更新人和更新时间。 - 旧的
completedRequirementIds继续保留用于兼容历史数据,但当同一需求存在requirementCoverage时,以新覆盖状态为准。 - 产品/UI 计划的成果提交门禁只认
completed,partial只记录进度,不满足“关联需求全部覆盖”。 - 计划增加
logs[],记录需求进度更新和 AI 拆解触发/完成/失败,右侧日志时间线消费该数据。
理由:覆盖状态是产品/UI 计划自身的业务事实,不应该用简单 checkbox 表达。显式记录“已完成/剩余”能支撑日报、复盘和需求完成质量分析,同时保留旧字段可避免历史数据迁移成本。
36. 小宝预警规则优先,AI 只做解释
问题:如果直接让 AI 判断版本能否发版,模型可能忽略系统内的任务、Bug、测试、日报和权限事实,结论不可追溯;如果只按风险等级触发 AI,又会漏掉同等级内风险剧变,例如 P1 Bug 从 0 到 3、测试失败、发版日只剩 1 天。
决策:
- 小宝预警先由确定性规则计算
riskScore、riskLevel、forecastReleaseDate、confidence、趋势、静默风险和证据摘要。 on_track不触发 AI;at_risk、likely_delayed、blocked自动触发 AI;attention只有在风险分、趋势、关键 Bug、失败用例、阻塞、静默风险、置信度或预测日期出现明显恶化时触发。- AI 解读自动触发,不提供人工“AI 解读”按钮。缓存签名必须覆盖趋势、原因、静默风险、日报/活动证据、风险信号和置信度,避免复用过期解读。
- 快照保存需要节流:同版本同日普通变化 10 分钟内不重复保存;风险等级变化、风险分变化达到阈值、关键 Bug/失败用例/阻塞/静默风险变化或预测日期明显变化时立即保存。
- AI 解读需要 cooldown:同版本最近 6 小时内已有解读时不重复请求;如果风险等级升级,则允许绕过 cooldown。
- AI 只写入
xiaobao-risk-insights缓存,不修改 Version、DevTask、TestCase、Bug、Requirement 或 Member。
理由:规则结果可测试、可追溯、可复盘;AI 文案提升可读性,但不能替代系统事实判断。趋势、静默风险和置信度能弥补“当前风险等级”过于静态的问题。
37. AI 拆解支持无需求ID分组,不自动补需求池
问题:0 到 1 项目中,需求池可能只录入一条宽泛需求,例如“做一个充值功能”,而原型批注里已经包含更多细粒度功能。旧规则把“原型里有、关联需求里没有”的批注只放进 noteOnly 报告,不拆任务,会漏掉真实开发和测试范围。若让 AI 自动创建 Requirement 又会污染需求池和版本关联需求列表,让“正式需求范围”和“原型推断内容”混在一起。
决策:
- AI 拆解继续优先按关联需求匹配;匹配成功的草案带正式
requirementId。 - 原型中明确可拆、但没有匹配到关联需求的批注,进入
prototypeOnly,也可以生成 DevTask / TestCase 草案。 prototypeOnly草案不创建 Requirement,不加入需求池,也不加入版本关联需求列表。- 这类草案写入开发任务/测试用例时,
requirementId为空,使用requirementName作为展示分组名,列表标题为无需求ID · {requirementName}。 - 不额外展示“原型发现”标记;只有既有
aiDraft草案视觉状态。 - DevTask 需要直接归属版本(
versionId),requirementId改为可选;旧数据可继续通过requirementId -> Requirement.versionId兼容推导。 - 版本级统计、工作台和风险预警统计包含无需求ID分组任务/用例;需求级进度和需求关闭条件只统计带正式
requirementId的 DevTask。
理由:关联需求代表人为确认的正式范围,不能被 AI 静默扩写;但原型批注也是当前版本真实交付范围的重要证据,不应该因为没有 REQID 被丢掉。无需求ID分组让任务和用例完整进入执行视图,同时保持需求池干净、关联需求列表可信。
38. 生产部署采用 Docker Compose + Nginx 同域反代
问题:系统已经从浏览器 localStorage 迁到 NestJS + PostgreSQL app_data,可以部署到云服务器,但如果只依赖开发命令,生产环境会缺少统一编排、健康检查、持久卷、反向代理和数据库初始化流程。
决策:
- 生产部署使用
docker-compose.prod.yml编排web、server、postgres、redis、nginx。 - 本地服务器/局域网部署使用
docker-compose.local.yml,同样跑完整五服务栈,但默认绑定宿主机8080,并使用独立local_*volumes。 web和server分别用Dockerfile.web、Dockerfile.server从 monorepo 根目录构建,先构建@ftb/shared,再构建各自应用。- 对外只暴露 Nginx 80 端口;
/api/转发到 NestJS,其他路径转发到 Next.js。 - 前端生产默认使用同域 API:
NEXT_PUBLIC_API_URL=/api/v1,避免浏览器跨域配置。 - PostgreSQL、Redis 和 server 文件数据分别使用
postgres_data、redis_data、server_data命名卷持久化。 - 数据库生产初始化走
prisma migrate deploy,仓库保留初始 migration;本地开发仍使用prisma migrate dev。 - HTTPS 先交给云负载均衡、CDN、宿主机证书工具或外层 Nginx 终止;Compose 内置 Nginx 保持 HTTP 反代基线。
理由:Docker Compose 足够覆盖当前单机云服务器和本地服务器形态,部署成本低、可读性强,也符合现阶段 2 核 4G 云主机目标。同域反代能减少 CORS 和公网端口暴露面。本地服务器默认 8080,避免占用 80 端口或要求管理员权限;云服务器继续使用 80 作为外层入口。HTTPS 证书自动续期和域名接入在不同云环境差异较大,先作为外层能力处理,避免把生产部署模板绑死在某一种证书方案上。
38.1. 生产发布改为 CI 镜像制品 + 运行版本校验
问题:仅让部署侧 git pull 最新代码并不能保证线上用户看到新前端。Next.js 前端需要重新构建,Docker 容器也需要重新创建;如果对方只拉代码、不 build/up,就会出现仓库是新的、运行容器仍是旧的情况,需求池搜索、成员用户名、性能改动等都无法靠肉眼判断是否已经上线。
决策:
- GitHub Actions 在
master更新时构建web/serverDocker 镜像,并推送到 GHCR。 - 镜像以 commit SHA 作为不可变 tag,同时更新
mastertag。 docker-compose.prod.yml使用WEB_IMAGE/SERVER_IMAGE拉取镜像,保留build仅作为本地兜底。APP_VERSION使用 commit SHA,构建时写入前端NEXT_PUBLIC_APP_VERSION和后端运行环境。- 后端提供
GET /api/v1/health/version,Actions 发布结束后必须校验返回版本等于本次 commit SHA。 - 前端定时比较自身构建版本和服务端运行版本,不一致时提示刷新页面。
理由:发布物必须是 CI 产出的镜像,而不是服务器上的源码目录。版本号打进镜像后,部署问题可以被机器判断:如果 Actions 校验通过,说明线上容器已经运行本次提交;如果校验失败,问题就在部署链路而不是业务代码。前端刷新提示解决的是浏览器仍持有旧 bundle 的尾部问题,不能替代容器更新,但能让用户明确知道需要刷新。
39. AppData 文档写入采用乐观锁,先阻止静默覆盖
问题:V2.1 阶段业务数据仍按模块存成 app_data.value 整份 JSON 文档。多人同时打开同一模块后,如果 A 和 B 都基于旧副本编辑,原来的无条件 upsert 会让后保存的人覆盖先保存的人,尤其是任务、Bug、成员等高频写入数据。
决策:
GET /api/v1/data/:key返回version,由AppData.updatedAt.toISOString()派生;空文档返回version: null。- 前端
server-data.ts在读取和保存成功后缓存每个 key 的最新version。 - 前端保存已读取过的 key 时提交
{ value, version };尚未读取过的兼容路径仍可提交{ value }走旧式 upsert。 - 后端收到字符串
version时使用updateMany({ where: { key, updatedAt }, data: { value } })原子比较并更新;count !== 1返回409 APP_DATA_CONFLICT。 - 后端收到
version: null时只允许创建不存在的行;如果其他客户端已经创建,返回同样的409 APP_DATA_CONFLICT。 - 冲突响应返回
currentVersion和currentValue,但当前前端不自动合并、不自动重试,避免把旧本地副本用新版本号再次覆盖服务端数据。
理由:这是 AppData 阶段成本最低、收益最高的一致性补强。它不能提供字段级协同编辑,但能阻止最危险的“静默最后写入覆盖”。后续拆成关系表和领域 API 后,再在具体实体上做更细粒度的事务、唯一约束、审计日志和冲突合并 UI。
40. AI 拆解借鉴 Superpowers 的交付切片和测试倒逼粒度
问题:AI 原型拆解虽然已经要求细颗粒任务和测试用例,但模型仍可能把清晰 QY 批注压成一条大任务、一条“完整流程验证”,或者把带详细说明、字段和验收标准的批注误判为含糊。
决策:
- Prototype Decompose Agent 在生成 DevTask/TestCase 前,必须先按 UI、交互、接口、数据、异常、边界、状态流转、兼容和回归做内部交付切片。
- 每条业务规则、字段规则、交互规则、异常规则和验收标准,必须至少映射到 1 条开发任务或 1 条测试用例;本次 target 不包含的一侧可以不输出,但另一侧必须覆盖。
- DevTask
description从可选变为必填,至少包含实现范围和验收点。 - TestCase
description必须包含前置条件、操作步骤和预期结果;涉及边界、异常或数据一致性时写明测试数据或状态。 - 原型批注只要包含标题、详细说明、字段、异常或验收标准之一,且能判断用户动作或系统行为,就不得进入
ambiguous;必须进入matched或prototypeOnly继续拆解。
理由:Superpowers 的任务计划和 TDD 流程本质上是“先把工作拆成可验证切片,再用测试约束倒逼粒度”。把这套方法沉淀到 Agent 契约里,能减少粗拆、漏拆和误判含糊,同时不改变现有数据模型,只提高 AI 草案的结构质量。
41. AI 拆解任务类型不受人工字典限制,采纳时自动入库
问题:任务类型字典最初服务于人工新建任务表单,覆盖的是常见开发和测试类型。AI 从原型里识别出的真实交付切片可能更细,例如“人员名片交互”“预入职数据源”“姓名展示兼容测试”。如果继续要求 AI 只能输出既有 categoryCode 枚举,就会把拆解能力绑死在人工表单选项上,字典没覆盖时容易漏拆或被迫归到错误类型。
决策:
- AI DevTask / TestCase 草案必须输出
taskTypeName,这是展示给用户的可复用任务类型名称,不是任务标题或当前文档的场景概述。 categoryCode改为可选兼容映射;只有能明确对应已有稳定语义码时才输出,不再作为 schema 必填项,也不再限制为固定枚举。- 前端采纳 AI 结果时,按
taskTypeName在当前TaskCategory同分组内查找;可复用开发类型不存在时自动追加一条isSystem=false的任务类型,并用该类型的id写入 DevTask。测试用例未知类型不自动入库,优先按categoryCode或默认测试类型回退。 - 去重逻辑按任务类型名称、标题和引用来源判断;已采纳过的自定义 AI 类型再次生成时也能过滤重复草案。
- 人工新建任务表单继续使用任务类型字典作为可选项,但这个字典会随着 AI 草案采纳自动扩展。
理由:人工表单的任务类型是录入辅助,不应该成为 AI 拆解边界。但任务类型字典也不能被单个原型的业务对象污染。taskTypeName 只承载可复用分类,具体功能点放在 title 和 description;采纳时有门槛地补字典,让后续筛选、统计和人工创建复用真正稳定的类型,同时避免 AI 直接写数据库 categoryId。
42. V2.2 高增长业务表从一开始采用分区表
问题:需求池、开发任务、测试用例、BUG、工作活动和小宝快照会按年持续增长。若先用普通表,后续再切分区表,不只是搬旧数据,还会牵动主键、唯一约束、外键、迁移窗口和回滚方案。
决策:
requirements按product_id做固定 HASH 分区,主键为(id, product_id)。dev_tasks/test_cases/bugs按version_id做固定 HASH 分区,主键为(id, version_id)。work_activities/task_worklogs/overtime_records/xiaobao_risk_snapshots/xiaobao_risk_insights/ai_logs按created_at做 RANGE 分区并保留 default partition。- 所有分区表主键和业务唯一约束必须包含分区键,例如
(version_id, code)。 - 引用分区表时优先使用复合外键,例如
(requirement_id, requirement_product_id)、(test_case_id, test_case_version_id);多态活动记录使用逻辑外键。 xiaobao_risk_summaries不分区,保持version_id单行汇总;历史趋势进入分区快照表。
理由:把分区键纳入主键和唯一约束是 PostgreSQL 分区表的结构性要求。提前做这件事,可以避免客户数据变大后再重塑主键和外键。固定 HASH 分区避免每个项目/版本单独建分区的维护负担;RANGE 分区只用于天然追加、按时间维护的历史数据。
43. V2.3 AppData 写入后非阻塞同步关系表
问题:V2.2 已经让版本详情、需求池、与我相关和小宝预警优先读取关系表,但前端保存仍然写 AppData。如果关系表不跟随 AppData 更新,快读路径会逐渐变旧,最后又回落到加载大 JSON 文档,无法解决几十万条需求/任务/用例后的卡顿风险。
决策:
- AppData 继续作为兼容窗口内的写入事实源,
DataService.put()先完成乐观锁写入,再触发关系表同步。 - 同步服务独立为
AppDataV23SyncService,复用 V2.2 mapper,不把一次性迁移服务改造成在线写入服务。 - 当前态表按分区键作用域替换:需求按
product_id,版本计划/开发任务/测试用例/BUG 按version_id。 - 追加型证据表继续
createMany(skipDuplicates),不因当前 AppData 文档缺失而删除历史。 - 小宝摘要由快照刷新;计划/任务/用例/BUG/活动/工时/加班变更只标记摘要
dirty=true,等待下一次规则计算刷新完整内容。 - 同步失败只记录日志,不阻塞 AppData 保存。慢 API 和慢 Prisma 查询先通过日志监控,后续再接 Prometheus/Grafana。
理由:这是从 AppData 兼容写入平滑过渡到领域 CRUD 的中间层。用户保存不能因为派生关系表暂时失败而丢失业务数据;同时,关系表保持跟随更新后,V2.2 快读路径才能真正承受大数据量。把同步服务独立出来,也能让后续领域 CRUD 逐步替换 AppData 时复用同一套映射和小宝 dirty 策略。
44. V2.4 领域 CRUD 成为主写入路径,AppData 退为兼容兜底
问题:V2.2/V2.3 让高增长页面优先读关系表,但前端主写仍长期停留在 AppData 时,会形成“AppData 写入 + 关系表同步”的双层事实链。数据量继续增长后,需求池分页搜索、版本详情、与我相关和小宝预警仍会受 AppData 同步时效、整文档写入和双源理解成本影响。
决策:
- V2.4.0 先统一共享状态契约,避免领域 API 切换时把旧状态机重新带回系统。
- V2.4.1-V2.4.4 将 Product、Project、Version、Requirement、VersionPlan、DevTask、TestCase、Bug 切为领域 API 主写。
- V2.4.5 将 Member、TaskCategory、TaskWorklog、OvertimeRecord、WorkActivity 切为领域 API 主写。
- 需求池列表/search/filter/sort 走服务端分页,避免加载全量 AppData 文档。
- 版本详情实体按
versionId分区键写入;Requirement 按productId分区键写入;追加型证据表保留追加语义,不从 AppData 快照反向删除历史。 /workspace和/xiaobao-warning继续走关系表聚合/快读。领域写成功后通过工作活动和小宝 dirty 标记维持证据链。- AppData 保留为兼容读取、失败回退、历史迁移和少量配置承载,不再作为已迁移领域的事实源。
- 成员迁移采用保守边界:
users存成员身份字段;部门、角色、密码规则暂不在本阶段发明完整 RBAC 表,仍作为 AppData 兼容配置。 - 加班原因同样暂留 AppData 配置;加班记录本身写
overtime_records。
理由:
- 领域 CRUD 直接写关系表后,读写路径对齐,分页、筛选、聚合和风险预警不再依赖 AppData 同步是否及时。
- 分区键进入每次领域写入,能维持 V2.2 分区表设计的查询边界。
- AppData fallback 让迁移可回滚、可兼容旧数据,但不再制造长期双事实源。
- RBAC/配置表会影响权限模型和管理流程,单独成阶段更安全;V2.4.5 只收口当前高频业务写入,避免为了“全收口”临时设计不稳的权限 schema。
45. V2 后端关系化采用八阶段交付链路
问题:V2.1/V2.2/V2.3 已经分别解决了服务端持久化、关系表快读和 AppData 写入后同步关系表。后续如果只写“AppData 退场、权限审计、性能增强、企业能力、生产稳定”这些大方向,容易出现三个偏差:V2.4 写领域 CRUD 时没有提前埋权限和审计,V2.5 把 AppData 当成可直接删除的旧表,V2.6 才发现分页、索引和查询边界没有在 API 设计期处理。
决策:
- V2.1:AppData 服务端持久化,先把业务数据从浏览器 localStorage 迁到服务端。
- V2.2:关系表 + 分区 + 快读 API,优先支撑版本详情、需求池、工作台和小宝预警等读热点。
- V2.3:AppData 写入后同步关系表,兼容期内保证快读数据跟随更新。
- V2.4:领域 CRUD 主写迁移。每个领域 API 从第一版就必须带资源作用域、当前用户、
actorId、基础审计事件入口、分页、索引和分区键查询边界。 - V2.5:AppData 分阶段退场 + RBAC/审计/一致性收口。退场顺序固定为“禁写 → 双读核对 → 移除 fallback → 只读归档/导出 → 后续删表”。
- V2.6:大数据性能增强 + 小宝预警后台化。在关系表主源稳定后做压测、慢查询治理、缓存/摘要、后台任务、幂等、锁和失败重试。
- V2.7:企业级协作能力 + 管理治理,补通知、协同、组织治理、管理视图和企业级配置。
- V2.8:生产硬化稳定版 + 运维闭环。生产部署基线已存在,V2.8 聚焦备份恢复演练、发布 smoke test、监控告警、日志检索、迁移回滚和运维手册。
理由:这条链路保持了从低风险兼容到强一致主源的顺序。权限/审计必须随领域 CRUD 进入代码路径,否则后补会重写接口边界;AppData 退场必须有闸门和回滚价值,不能直接删除;性能基础要从 V2.4 的 API 设计开始,V2.6 只做规模化增强和后台化能力。这样每个阶段都有清晰验收物,也能避免长期双主源、无审计写入和大数据查询返工。
46. 业务主数据源切换到 PostgreSQL 领域关系表
问题:AppData JSONB 文档表解决了浏览器 localStorage 丢数据问题,但如果继续把 app_data.value 当长期主数据源,会带来三个风险:整份 JSON 写入难以做字段级事务和权限校验,大数据量下筛选/分页/统计仍要依赖派生同步,线上排查时容易出现 AppData 与关系表不一致。
决策:
- PostgreSQL 领域关系表是后续业务主数据源,AppData 只保留为迁移、回填、兼容读取和审计排查入口。
- 新增业务模块不得新增 AppData key 作为主存储;必须先设计关系表、Prisma model、领域 CRUD API 和必要的索引/分区键。
- 现有 AppData key 按领域逐步迁移:先补关系表写 API,再让前端 store 写领域 API,最后移除对应
saveServerData/loadServerData主路径。 - V2.2 快读失败时的 AppData fallback 只能作为迁移期兜底,不能通过硬编码
usingV22=false长期绕开关系表。 - 删除或停用 JSON 文档前必须完成数据备份、迁移计数核对、抽样校验和回滚预案。
- 一次性同步脚本可以存在,但必须作为运维迁移工具管理,不能依赖提交
.env或手工修改源码开关。
理由:系统未来要承载大量需求、任务、测试用例、Bug、活动和风险数据。关系表才能提供可验证的约束、事务、索引、分页、权限和审计能力。AppData 是低风险迁移桥,不是最终架构;继续扩大 JSON 主存储会把数据一致性和性能问题推迟到更难修的阶段。
47. V2.5 用冻结、审计和一致性校验收口迁移期,而不是直接删除 AppData
问题:V2.4 已经把主要领域写入迁到关系表,但 AppData 里仍保存历史 JSON、旧部署 fallback 和少量尚未领域化的配置形状。如果直接删除 app_data 或移除 /data/:key,会失去回滚、迁移核对和历史排查依据;如果继续允许写入,又会把双事实源问题拖进 V2.6。
决策:
- 每个 AppData key 明确进入
write_frozen或read_only_archive,由AppDataRetirementService集中配置替代 API 和说明。 - 冻结 key 的
PUT /api/v1/data/:key返回409 APP_DATA_WRITE_FROZEN;GET继续可用,用于历史读取、归档导出和人工核对。 - Product/Project/Version/Requirement/VersionPlan/DevTask/TestCase/Bug/Member/TaskCategory/TaskWorklog/Overtime/WorkActivity 等领域 mutation 全部使用
@ProtectedMutation(),同一个装饰器组合权限、资源作用域和审计写入。 audit_events采用 append-only 模型,按created_at分区;敏感字段由 AuditService 脱敏,查询接口需要audit:view。- 一致性校验同时提供
GET /api/v1/consistency和pnpm consistency:v25,检查 counts、分区键、孤儿引用和审计覆盖。历史数据缺少审计事件只作为 warning,不把迁移前事实误判为当前写路径错误。 - 当前 server auth context 先用
x-ftb-user-*头作为稳定 adapter,前端从现有登录会话补齐这些头;正式 JWT/NextAuth 服务端验证留给后续认证治理阶段。 - Xiaobao risk snapshots/insights 的 AppData key 进入
read_only_archive,关系表写入和后台化归 V2.6;xiaobao-warning-views读状态 API 归 V2.7。
理由:冻结写入能立即切断新的双主源风险,同时保留旧 JSON 的审计和回滚价值。把权限和审计合并到领域 mutation 装饰器,可以确保后续新增写接口默认带服务端 guard 和 audit event。审计覆盖对历史数据只告警,避免为了“补齐历史审计”伪造事件。auth header adapter 给 V2.5 一个可测试的服务端权限边界,但不把它包装成最终安全方案,后续 JWT/企业 RBAC 可以替换 adapter 而不改领域 controller 合同。
48. V2.6 后台任务先采用 PostgreSQL Lease 队列
问题:小宝风险摘要、AI 解读和后续通知都需要在用户不打开页面时后台刷新。直接把这些逻辑放在页面 effect 中会导致无人访问时数据不更新;直接引入 Redis queue 又会增加一套可靠性、幂等和迁移运维面。
决策:
- 新增
background_jobs表和JobsModule,作为 V2.6 后台任务运行时。 - Job 行包含
type、payload、dedupe_key、status、attempts、max_attempts、available_at、locked_by、locked_until、last_error。 - 同一
type + dedupe_key在queued/running状态下唯一;服务层先查 active job,遇到并发唯一冲突再回读,保证 enqueue 幂等。 - Worker claim 使用数据库事务、
FOR UPDATE SKIP LOCKED和 lease 时间;running且locked_until过期的 job 可以被新 worker 回收。 - Handler 失败时按
attempts < max_attempts重回queued并设置下一次available_at;达到上限后进入failed,只记录错误,不修改业务实体。 BackgroundJobWorker只负责 handler 注册和单次执行,业务副作用仍放在各领域 service 内,避免队列层知道小宝、通知或审计细节。
理由:PostgreSQL 队列足够支撑 V2.6 的低频后台刷新,同时能和领域写入共享事务边界、唯一约束和迁移流程。等 V2.7 通知或更高吞吐任务落地后,如确实需要 Redis/专用队列,再通过同一 JobsService 接口替换底层实现,而不是现在提前引入第二套事实源。
49. V2.6 小宝风险摘要改为服务端后台刷新
问题:小宝预警最初由页面加载完整前端 store 后计算并保存快照/缓存。这样会导致没人打开页面时 xiaobao_risk_summaries 不刷新,侧边栏和 V2.2 快读只能看到旧风险。
决策:
- 新增
XiaobaoModule,包含XiaobaoRiskService、XiaobaoRiskWorker和最小 controller。 - 服务端先移植确定性规则的核心口径:剩余开发/测试/Bug 工作量、关键缺陷、阻塞项、失败用例、预测延期、置信度和 risk signature。
XiaobaoRiskService.markDirtyAndEnqueue(versionId)负责 upsert dirty summary 并排入xiaobao.summary.refresh,dedupe key 使用versionId。XiaobaoRiskWorker通过 V2.6BackgroundJobWorker注册 handler,执行时只刷新xiaobao_risk_summaries,不修改 Version、Requirement、DevTask、TestCase、Bug 或 Member。- 领域写入侧继续通过
WorkActivityService.markXiaobaoSummaryDirty()收口;普通 update/delete 没有 activity 证据时显式标脏,避免风险摘要漏刷新。
理由:把 deterministic summary 放到服务端后,读路径不再依赖页面打开,且所有前端仍可沿用 V2.2 summary API。AI 解读仍是后续独立队列,只消费 summary/signature 并写 insight cache;本决策不让 AI 或后台 worker 直接改业务实体。
50. V2.6 小宝 AI 解读改为服务端队列,只写 insight cache
问题:小宝 AI 解读原先由 /xiaobao-warning 页面触发。即使 V2.6 已经把 deterministic summary 刷新移到服务端,如果 AI 解读仍依赖页面打开,高风险版本在无人访问时仍不会产生新的解释缓存,也不利于后续 V2.7 通知使用同一解读结果。
决策:
- 新增
XiaobaoAiModule,通过XiaobaoAiService和XiaobaoAiWorker注册xiaobao.ai.interpretjob。 XiaobaoRiskService.refreshSummary()upsert summary 后调用XiaobaoAiService.evaluateSummary(),按 policy 判断是否排入 AI 解读 job。- AI 触发策略复用页面规则的核心边界:
at_risk、likely_delayed、blocked可触发;精确riskSignature命中时复用缓存;同版本最近 6 小时内已有解读时 cooldown;风险等级升级可绕过 cooldown。 - 服务端当前没有完整前端趋势快照上下文,因此
attention只在“距离预期发版日小于等于 1 天且仍有未完成工作”时触发。趋势、置信度下降和明细信号变化的完整 attention 策略等待服务端趋势快照补齐后再扩展。 - Worker 执行时重新读取
xiaobao_risk_summaries,若 job payload 的riskSignature已过期则跳过,避免为旧风险写新解释。 - AI 调用只走现有
AiService.interpretRisk()和 risk prompt/provider 抽象,不新增 SDK 调用、不绕过 AI 配置。 - AI 成功后只写
xiaobao_risk_insights,缓存保存时间使用服务端now,不信任模型返回的generatedAt作为缓存新鲜度;失败抛错交给 background job retry。 - Worker 不修改 Version、Requirement、DevTask、TestCase、Bug、Member 等业务实体,也不写通知。V2.7 通知如需消费结果,应通过 insight cache 或 adapter 读取。
理由:AI 解读是对确定性规则结果的解释层,不是业务事实源。把它做成 summary 后置队列,能让无人打开页面时也生成解释,同时通过 signature/cooldown/escalation 控制成本和重复调用。只写 cache 能保持 AI 与业务实体解耦,后续通知和审计可以复用缓存,而不是让 AI worker 直接参与业务状态流转。
51. V2.6 运维看板先做轻量运行时快照,RBAC 通过 adapter 衔接
问题:V2.6 增加了性能 harness、后台 job runtime、小宝 summary refresh 和 AI 解读队列。如果没有一个运行时入口,慢请求、慢查询、job 堆积和 dirty summary 数只能从日志或数据库手工排查。与此同时,V2.5 后端 RBAC/audit 合同尚未落地,不能为了看板临时硬编码一套后端权限结构。
决策:
- 新增
OpsModule,提供GET /api/v1/ops/runtime,返回慢请求、慢 Prisma 查询、后台任务队列、失败任务和 dirty summary 数。 - 慢请求继续由
ApiTimingInterceptor识别;慢查询继续由PrismaServicequery event 识别。二者额外写入进程内 ring buffer,作为轻量 dashboard 数据源。 - 看板只保留最近事件,不做长期审计。长期审计和多实例聚合等待 V2.5 audit 或后续 observability 方案。
- 请求 URL 去掉 query string;SQL 只展示截断后的 query preview;
sk-*、token、secret、password、authorization 等 key-like 文本统一 redacted;不展示 AI provider apiKey、请求参数或环境变量。 - Job 队列从
background_jobs最近 200 行聚合,按 type 展示 queued/running/succeeded/failed,并展示最近 failed job 的脱敏lastError。 - 前端
/admin/ops使用RouteGuard permission="ops:view";权限字典新增ops:view,但不默认授给非管理员 preset。后端通过OpsPermissionAdapter保留ops:view校验入口,待 V2.5 RBAC guard 落地后替换。
理由:当前目标是让 V2.6 的性能和后台化能力可观察,而不是建设完整监控平台。进程内 ring buffer 成本低、对生产数据无额外写放大;结合脱敏规则可避免把 secrets 带进管理端。权限 adapter 明确了未来替换点,避免 Ops 看板和未定型 RBAC/audit 合同互相绑死。
52. V2.7 协作治理先落稳定适配器,不硬编码临时权限
问题:V2.7 需要通知、评论、项目成员治理、管理驾驶舱和治理字典。如果各 V2.7 模块直接写临时权限判断和审计插入,就会绕开 V2.5 已落地的服务端权限、审计和资源作用域边界,后续认证治理也会再次返工。
决策:
- 新增
RbacService作为项目角色与全局权限断言适配器,Owner/Admin/Member/Viewer 的层级判断和management:view/governance:manage等全局权限入口集中在此处。 - 新增
AuditService作为审计写入适配器,业务模块只提交actorId/action/resource/before/after。 - 通知事件类型固定为
assignment / mention / risk_alert / overdue_item,跨模块通过这些稳定语义发通知。 - 通用评论使用
entityType + entityId + entityVersionId的多态引用,不给每个业务表单独建评论表。 - 管理驾驶舱只读关系表和
xiaobao_risk_summaries,不回读 AppData。 - 治理字典使用软删除或使用中禁止硬删,变更必须写审计。
理由:适配器把协作治理模块的权限和审计接入点收束在一层,既能复用 V2.5 的服务端控制面,也给后续 JWT/NextAuth 和企业级角色体系留下替换点。稳定事件名和多态评论引用能避免后续模块继续扩散 ad-hoc 字段。
53. Business Analysis Agent 采用语义层、指标目录和受控分析计划
问题:下一阶段需要让用户用自然语言围绕产品、项目、版本、需求、部门和用户多维度提问,并自动生成图表与分析报告。如果只做“问题 -> 固定模板 -> 查询”,后续会被模板数量卡住;如果让 AI 直接决定查询或生成 ECharts option,则会带来权限绕过、口径不一致、不可复现和难以维护的问题。
决策:
- 新增独立 Business Analysis Agent,只读业务数据,不修改任何业务实体、不创建草案、不触发状态流转。
- 自然语言先进入 Semantic Layer,把“忙 / 压力 / 风险 / 延期 / 效率 / 质量 / 需求完成”等业务说法映射到受控
metricId、dimensionId、analysisType、timeIntent和scopeIntent。 - Semantic Layer 输出内部
semanticConfidence;前端只展示高/中/低置信,不展示伪精确百分比。数据充分性另用dataConfidence表达。 - 建立 Metric Catalog,记录 metric
version、公式、owner、支持维度、支持分析类型、默认图表、默认维度和默认时间口径。只要公式或计算口径变更,就提升 metric version;纯展示变化不升版本。 - 分析策略分三层:Template Strategy 优先命中高频模板;Rule Composition Strategy 是确定性系统规则组合;AI Planning Strategy 只在前两者无法覆盖时生成
AnalysisPlan建议。 - AI 生成的
AnalysisPlan必须只使用 Semantic Layer / Metric Catalog 暴露的指标、维度、筛选和聚合能力,并经过 Analysis Plan Processor 校验与规范化后才能执行。 - Analysis Plan Processor 不只校验,也负责 Normalize,将模板、规则组合和 AI proposal 统一成标准
AnalysisPlan,Metric Engine 只消费统一格式。 - Metric Engine 输出统一
MetricResult。ChartSpec Builder、Insight Engine、Report Builder 和 Follow-up Builder 并行消费同一份 MetricResult,避免图表和报告互相耦合。 - ChartSpec 是平台统一契约,不是 ECharts option。前端第一版用 ECharts Renderer 渲染,未来可替换为其他图表引擎。
- Evidence 不是纯 chips,而是可点击数据证据:包含 label、value、sourceDomain 和 drilldown filters。
- Report 固定为 Summary / Key Findings / Evidence / Suggestions / Data Scope,避免不同分析回答格式漂移。
- Follow-up 分为 question、drilldown、export。第一版只允许只读追问、明细跳转和导出,不允许创建会议、分配负责人等写操作。
- 没有数据时走 No Data Strategy:说明请求、范围、缺少的数据和可替代分析,不让 AI 编造解释。
- 权限红线:Analysis Agent 只能查询当前用户已有权限的数据,自然语言不能扩大范围;无权限时拒绝或返回授权范围内的空结果。
理由:Semantic Layer 和 Metric Catalog 能把自然语言、业务口径和数据库字段解耦;metric version 和 result snapshot 能支撑历史分析复现;Analysis Plan Processor 保证 AI proposal 不直接变成系统执行;统一 ChartSpec 和 MetricResult 让 ECharts 只是当前 renderer,而不是长期数据契约。这样第一版可以靠固定模板稳定交付,后续又能通过确定性组合和受控 AI Planning 扩展能力。