diff --git a/AGENTS.md b/AGENTS.md index ff114603..1d7e6c84 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -124,3 +124,20 @@ Deployment safety rules: 2. 不得在 `git ls-remote` / fetch / push 等远端验证失败时声称云端已同步。 3. 不得在未查看当前 `git status --short --branch` 时覆盖或重置本地变更。 4. 新发现的重复错误、阻塞、碎片目录、远端验证失败,必须追加到错误台账或当轮 sweep 文档。 + +## 6. Bug History Workflow Hard Constraint + +任何包含“Bug、报错、失败、异常、回归、无法保存、无法删除、状态码错误、线上故障”等现象的任务,开始诊断前必须完整读取并搜索: + +- `docs/BUG_HISTORY.md` + +这份文件是产品与代码 Bug 的长期历史入口;`docs/research/pre_work_error_ledger.md` 继续负责基础设施、外部引擎、碎片目录与预检风险,两者不得互相替代。 + +执行要求: + +1. 修复前必须用报错原文、接口路径、状态码、模块名和用户操作搜索历史记录,优先检查相同模块的根因与防复发措施。 +2. 若历史问题复发,必须关联原 `BUG-NNN`,说明旧测试、约束或发布门禁为何未拦住;不得把复发伪装成无关的新问题。 +3. Bug 修复必须在同一变更中更新 `docs/BUG_HISTORY.md`:补充现有记录或新增连续编号,并记录状态、现象、触发条件、根因、修复、验证、防复发、关联记录和修复版本。 +4. 若当轮只能诊断或被阻塞,也要把已确认事实写成 `investigating` 或 `blocked`,不得编造根因或提前标记 `resolved`。 +5. `resolved` 必须有与风险相称的证据:至少一个针对性回归测试;生产问题还必须有脱敏后的迁移、部署、健康检查或 smoke 证据。 +6. Bug 历史严禁写入姓名、出生资料、邮箱、用户/案例 ID、Cookie、JWT、密码、密钥、完整请求体或模型原文。 diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md new file mode 100644 index 00000000..a3725507 --- /dev/null +++ b/docs/BUG_HISTORY.md @@ -0,0 +1,171 @@ +# Bug History + +本文件是本仓库产品与代码 Bug 的长期知识库。处理任何 Bug 前先读并搜索本文件;完成修复或确认阻塞后,在同一变更中更新本文件。 + +基础设施、外部引擎、碎片目录和开工预检类风险仍记录在 `docs/research/pre_work_error_ledger.md`。同一问题若同时影响两类台账,应互相引用,不复制大段内容。 + +## 使用流程 + +1. 用报错原文、接口路径、状态码、模块名和用户操作搜索本文件。 +2. 找到相似记录时,先验证既有防复发措施是否仍存在,再定位新的回归入口。 +3. 修复必须包含与风险相称的自动化测试;生产问题还要记录部署版本和脱敏后的生产验证。 +4. 完成后更新已有记录,或按下方模板添加新记录。复发问题必须填写 `复发自`,不能伪装成无关的新问题。 +5. 不记录姓名、出生资料、邮箱、用户/案例 ID、Cookie、JWT、密码、密钥、完整请求体或模型原文。 + +## 状态定义 + +- `investigating`:现象已确认,根因未确认。 +- `blocked`:缺少权限、环境或外部条件,当前无法继续验证。 +- `mitigated`:用户影响已被安全降级,但根因或全链路尚未闭环。 +- `resolved`:根因已修复,并有自动化或真实环境证据。 +- `regressed`:历史问题再次出现;必须关联原记录并说明旧防线为何失效。 + +## 新记录模板 + +```markdown +## BUG-NNN | 简短标题 + +- 状态:investigating | blocked | mitigated | resolved | regressed +- 首次发现:YYYY-MM-DD +- 最近更新:YYYY-MM-DD +- 影响面:页面、接口或模块 +- 用户现象:脱敏后的可观察现象 +- 触发条件:最小复现路径 +- 根因:已验证的技术原因;未知时明确写未知 +- 修复:实际采取的最小修复 +- 验证:测试、生产 smoke、迁移账本或监控证据 +- 防复发:测试、约束、监控或发布门禁 +- 相关记录:BUG-NNN / ERR-NNN;没有则写无 +- 复发自:BUG-NNN;首次出现则写无 +- 修复版本:Git SHA、迁移版本或待发布 +``` + +## 已记录问题 + +## BUG-001 | 对话删除不可用或只改变前端状态 + +- 状态:resolved +- 首次发现:2026-07-21 +- 最近更新:2026-07-21 +- 影响面:会话列表、`DELETE /api/sessions/[id]`、Supabase `chat_sessions` +- 用户现象:用户点击删除后对话无法真正删除,或删除交互与账户弹窗不一致。 +- 触发条件:登录后删除自己的一条历史会话。 +- 根因:删除曾缺少所有者约束的服务端入口和对应数据库授权,前端也没有统一的站内确认面。 +- 修复:增加所有者约束的服务端删除接口与数据库 grant,并统一站内确认弹窗。 +- 验证:`tests/test_session_management_entrypoints.py`、`frontend/tests/chat-session-delete-contract.test.ts`。 +- 防复发:删除必须经服务端所有权校验;测试同时锁定 API、迁移和前端入口。 +- 相关记录:无 +- 复发自:无 +- 修复版本:`e3d619c`、`65b8c42`、`5650ed1`、`fcf5794` + +## BUG-002 | Onboarding 首次请求 409、重复请求或读到旧问题 + +- 状态:resolved +- 首次发现:2026-07-21 +- 最近更新:2026-07-21 +- 影响面:`POST /api/onboarding`、首页入门问题缓存与恢复 +- 用户现象:出生资料已经填写,第一次请求仍返回资料未完成或 `pending`,随后又请求一次并返回缓存结果。 +- 触发条件:入门资料完成、缓存生成尚未结束或资料在并发生成期间发生变化。 +- 根因:缓存就绪/处理中状态没有完整绑定当前资料指纹,旧请求完成时可能覆盖新资料的生成结果。 +- 修复:用全部决策字段生成无明文 SHA-256 身份;领取和完成都使用版本、时间戳和 pending 身份 CAS,并校验账户所有权。 +- 验证:onboarding route/cache/candidate completion 测试矩阵,历史完整前端套件 455/455。 +- 防复发:缓存命中、pending、超时回收和完成写入必须绑定同一资料身份;任何旧请求只能返回安全 `pending`。 +- 相关记录:无 +- 复发自:无 +- 修复版本:`28d58d4`、`5ee925c`、`308e5d5`、`e13d595`、`346ec02`、`a9ffbd9` + +## BUG-003 | 浏览器原始报错 “The string did not match the expected pattern” 泄露给用户 + +- 状态:resolved +- 首次发现:2026-07-21 +- 最近更新:2026-07-21 +- 影响面:生时校正自动流程、候选时间确认、“都不符合”分支 +- 用户现象:选择候选时间或“都不符合”后直接看到浏览器英文 DOMException。 +- 触发条件:自动或手动生时流程中的底层浏览器/传输异常进入未归一化错误分支。 +- 根因:部分 journey effect 直接展示实现层异常信息,没有统一转换为安全、可操作的中文错误。 +- 修复:所有相关 mutation/effect 统一经 `birthTimeUserError` 归一化,屏蔽 DOMException、网络和语法实现细节。 +- 验证:`frontend/tests/birth-time-user-errors.test.ts` 包含该英文原文回归用例。 +- 防复发:禁止 `setError(caught.message)`;新增异步入口必须走统一错误映射。 +- 相关记录:无 +- 复发自:无 +- 修复版本:`a38a096` + +## BUG-004 | 点击生时校正后先进入中间卡片,失败时无法自然回到首页 + +- 状态:resolved +- 首次发现:2026-07-21 +- 最近更新:2026-07-22 +- 影响面:首页生时校正入口、首轮加载与恢复 UI +- 用户现象:点击入口后先看到“开始生时校正”或“正在恢复账户里的校正进度”卡片;依赖失败时停留在重试卡片。 +- 触发条件:首轮校时 RPC 尚未返回或返回失败。 +- 根因:页面在首轮有效 turn 生成前就切换到专用校正会话。 +- 修复:首轮生成期间保持首页;只有有效首轮 turn 返回后才进入校正会话,失败以首页输入区提示呈现。 +- 验证:`frontend/tests/consultation-entrypoint.test.ts` 及生产入口 smoke。 +- 防复发:会话切换以“首轮可展示结果”而非“请求已发出”为边界。 +- 相关记录:ERR-087 +- 复发自:无 +- 修复版本:`dc0077e` + +## BUG-005 | 保存出生时间返回 `PATCH /api/account` 500 + +- 状态:resolved +- 首次发现:2026-07-21 +- 最近更新:2026-07-22 +- 影响面:账户出生资料、Supabase `profiles` +- 用户现象:选择记录时间和误差后显示“暂时无法保存账户资料”,接口返回 500。 +- 触发条件:账户已被候选时间写入 reported 字段,之后再次编辑原始出生时间声明。 +- 根因:旧触发器把候选/活动时间复制为用户报告时间,同时数据库又把报告时间视为不可变字段。 +- 修复:报告时间保持可编辑,禁止候选时间反写原始声明,修复不一致历史行并增加来源/时间一致性约束。 +- 验证:迁移 `20260721140000` 已进入生产账本;生产合成账户修改与恢复均返回 200。 +- 防复发:候选、活动、用户报告时间保持独立语义;profile persistence 测试锁定迁移行为。 +- 相关记录:ERR-088 +- 复发自:无 +- 修复版本:`dc0077e`、`20260721140000_repair_reported_birth_time_revision.sql` + +## BUG-006 | 生时校正首轮在没有历史事件时进入确认态并返回 409 + +- 状态:resolved +- 首次发现:2026-07-21 +- 最近更新:2026-07-22 +- 影响面:`POST /api/birth-time-conversation` 首轮、计费释放 +- 用户现象:等待约一至两分钟后首轮返回 `action_conflict`,费用预留被释放。 +- 触发条件:技术扫描在零条历史事件时直接返回 `ready_for_confirmation`。 +- 根因:应用构建了 `confirming` 首轮,而数据库契约只允许首轮为 `active`;技术就绪错误绕过了三条有效历史事件的业务门槛。 +- 修复:所有技术包统一经过 `MINIMUM_SCOREABLE_EVENTS` 门禁;未满三条时清除 result ID,并保持 `pending_validation/active`。 +- 验证:核心 orchestrator/e2e 回归测试;生产首轮随后成功创建并收费一次。 +- 防复发:确认态只能由三条以上有效、已发生、可评分事件触发。 +- 相关记录:ERR-089 +- 复发自:无 +- 修复版本:`b8ed740` + +## BUG-007 | `finance` 证据通过应用校验但被数据库拒绝为 409 + +- 状态:resolved +- 首次发现:2026-07-21 +- 最近更新:2026-07-22 +- 影响面:生时校正技术包、事件证据、Supabase durable validators +- 用户现象:真实首轮计算完成后仍返回统一的 `action_conflict`。 +- 触发条件:D2/D11 产生 `finance` 建议主题,或摘要包含应用已支持的 `domain` 字段。 +- 根因:TypeScript 已支持 `finance` 和摘要 `domain`,初始 SQL 枚举及表约束仍是旧版本。 +- 修复:向前迁移同步 evidence request、life event、private candidate、public recap 和事件表约束。 +- 验证:迁移 `20260721150000` 已进入生产账本;迁移契约测试和生产首轮 smoke 通过。 +- 防复发:应用证据枚举变更必须同时更新 durable SQL,并由迁移契约测试检查。 +- 相关记录:BUG-006、ERR-090 +- 复发自:无 +- 修复版本:`b981c4e`、`20260721150000_align_conversational_finance_domain.sql` + +## BUG-008 | 后续证据把范围压得过窄后返回 503 + +- 状态:resolved +- 首次发现:2026-07-21 +- 最近更新:2026-07-22 +- 影响面:生时校正后续回答、技术包构建 +- 用户现象:首轮、“都不符合”、暂停恢复均正常,但提交后续明确事件时返回 `service_unavailable`。 +- 触发条件:事件评分产生的窄区间不足两个时间样本或不足两个可区分分盘主题。 +- 根因:这是“证据仍不足”的正常业务状态,代码却把它作为 `TypeError` 依赖故障终止。 +- 修复:显式分类区间区分度不足;撤回本次过度收窄,保留上一候选范围、评分证据和未确认状态,然后继续提问或安全保存范围。 +- 验证:核心回归 85/85;生产从失败点续跑通过;全新生产 smoke 覆盖资料、首轮、“都不符合”、暂停恢复、多事件、范围终态、计费和问题交接。 +- 防复发:任何新候选范围必须先满足技术证据契约;不满足时回退,不返回 503,也不伪造确定分钟。 +- 相关记录:ERR-091 +- 复发自:无 +- 修复版本:`b981c4e` diff --git a/tests/test_bug_history_workflow.py b/tests/test_bug_history_workflow.py new file mode 100644 index 00000000..f48e4618 --- /dev/null +++ b/tests/test_bug_history_workflow.py @@ -0,0 +1,24 @@ +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +AGENTS = ROOT / "AGENTS.md" +BUG_HISTORY = ROOT / "docs" / "BUG_HISTORY.md" + + +def test_bug_history_exists_with_required_entry_contract() -> None: + text = BUG_HISTORY.read_text(encoding="utf-8") + + assert "## 使用流程" in text + assert "## 新记录模板" in text + for field in ("用户现象", "触发条件", "根因", "修复", "验证", "防复发", "复发自"): + assert field in text + + +def test_agents_requires_read_before_and_update_after_bug_work() -> None: + text = AGENTS.read_text(encoding="utf-8") + + assert "docs/BUG_HISTORY.md" in text + assert "开始诊断前必须完整读取并搜索" in text + assert "必须在同一变更中更新" in text + assert "严禁写入" in text