feat(rectification): add durable case and evidence runtime

This commit is contained in:
Jesse
2026-08-11 16:18:53 +08:00
parent 60e2ce4fa4
commit d394dd0585
13 changed files with 3794 additions and 0 deletions
+27
View File
@@ -2744,3 +2744,30 @@
- 相关记录:BUG-159、ERR-020、ERR-021、ERR-022、ERR-024、ERR-025、ERR-026、ERR-104
- 复发自:无
- 修复版本:本地 staging 候选(未 push / deploy
## BUG-162 | 生时校正入口没有服务端 Case 状态,复用第一条校正 Session 且并发会重复创建
- 状态:resolvedlocal candidate;真实 PostgreSQL fixture 待远端 runner 执行)
- 首次发现:2026-08-12
- 最近更新:2026-08-12
- 影响面:生时校正首页入口、侧边栏校正 Session 恢复、Case/Session 绑定、并发打开、旧 Direct Agentic 数据映射
- 用户现象:点击首页“生时校正”时,只要历史存在任意校正 Session 就进入“继续”,不管它是进行中、已采用、已确认还是已关闭;侧边栏点击某条校正 Session 可能被复用逻辑带向另一条记录;双击或多标签并发点击可能创建多组 Case/Session;已完成校正 Session 还会阻止用户重新开始。
- 触发条件:使用 `hasRectificationSession` + `sessions.find(sessionType === "birth_time_rectification")` 的客户端猜测路径;历史 Direct Agentic Session 没有 V9 Case 状态。
- 根因:旧入口把“存在任意校正 Session”当作“可继续”,把客户端数组第一条校正 Session 当作权威;没有服务端 Case 状态机(resumable/terminal 由消息数、是否有候选推断),Session 与 Case 没有数据库级双向绑定与精确恢复,open/create 没有 requestId 幂等账本和同用户串行化,旧 Agentic 数据也没有一次性的 Case 回填与结果映射。
- 修复(V9 Agentic Domain lane):
1. 新增独立 Skill `skills/jyotish-birth-time-rectification`SKILL.md ≤200 行 + 5 个 references),方法源只在 Skill,系统提示词不再复制完整方法。
2. 新增 V9 领域 contractsCase 状态机(10 状态 + resumable/terminal 谓词 + 单向终态)、Evidence kind/date precision/append-only lineage、public receipt/activity allowlist、open request/response schemas`supersedeActive=true` 被 schema 与 RPC 双重禁止。
3. 新增向前业务迁移 `20260812010000_agentic_rectification_v9_runtime.sql`(只进 `frontend/supabase/migrations`,不进 `frontend/db/migrations`):`agentic_rectification_cases`(每用户最多一个 resumable 由 partial unique index 强制;Case/Session 双向一致由触发器+RPC+唯一索引保证)、`agentic_rectification_evidence`(原文 grounding、日期精度、revision lineage、confirmed 只能由服务器确认路径产生)、`agentic_rectification_turns`pending/completed/failed/retryable,禁止 reasoning)、`agentic_rectification_tool_receipts`(只存 fingerprint/phase/tool/status)、`agentic_rectification_open_ledger`requestId 幂等);`agentic_rectification_results` 前向扩展 `case_id/evidence_ledger_fingerprint/candidate_range_fingerprint/skill_version`;所有新表 RLS + 仅 service_role 授权,全部 RPC security definer + `search_path=''` 且仅 service_role 可执行;浏览器不传 userId、出生快照、range 或权限决定。
4. 一次性幂等 legacy backfill(迁移内执行,应用启动不隐式批量跑):engine_confirmed→confirmed、user_accepted only→candidate_accepted、有消息无选择→collecting_evidence/candidate_ready、重复空/旧→abandoned/superseded、每用户只保留最新实际 active(窗口函数+部分唯一索引),旧文本绝不生成 confirmed evidence;提供 `backfill_agentic_rectification_legacy_cases()` 与可复跑的 `verify_agentic_rectification_backfill()`
5. 新增 Case Service + 五个 API`POST /api/rectification/cases/open`homepage resume-or-create / session 精确恢复 / new 安全冲突)、`GET entry-summary`(首页 CTA 真值)、`GET cases/[caseId]`(脱敏投影,绝不返回 baseline_birth_snapshot)、`POST close``POST upgrade-skill`;profile 不完整不建案;错误不泄露身份、出生资料或 DB 原文。
- 验证:新增 `rectification-v9-contracts.test.ts`12)、`rectification-v9-case-service.test.ts`14)、`rectification-v9-migration.test.ts`(17 静态合同)本地共 43 项全部通过;`rectification-v9-database.test.ts`(真实 PostgreSQL 迁移 apply/re-apply、open 原子+幂等+resume、profile 门禁、所有权、terminal 只读、evidence 生命周期、backfill 状态分布/单 active/结果映射/幂等)在本机因无 Docker 按环境 skip(5 项),待远端完整 runner 执行;既有生时校正相关测试 60/60 通过;`tsc --noEmit` 对本 lane 文件零错误(全量 6 个既有错误全部位于未触碰文件);目标 ESLint 0 error 0 warning`git diff --check` 通过;`database-local-business.test.ts` 精确 public 表清单同步新增 5 张 v9 表并断言新迁移 appliedBUG-127/144 防复发)。
- 防复发:Case 状态、resumable/terminal、shouldStartOpening 必须只由服务端决定;侧边栏必须以精确 sessionId 恢复并校验所有权;open/create 必须走 requestId 幂等账本 + 同用户 advisory 串行化;新增 self-hosted 业务表必须同时更新全迁移 applied ledger 与精确 public 表集合,且只进 `frontend/supabase/migrations`;新页面/新 API 必须同步能力审计;浏览器任何入参都不允许携带 userId、出生资料、range 或权限决定。
- 相关记录:BUG-004、BUG-027、BUG-033、BUG-067、BUG-068、BUG-069、BUG-072、BUG-074、BUG-085、BUG-086、BUG-095、BUG-112、BUG-113、BUG-114、BUG-115、BUG-116、BUG-117、BUG-118、BUG-119、BUG-120、BUG-121、BUG-127、BUG-143、BUG-144
- 复发自:无(新 Agentic Domain 主链;旧防线缺失的直接原因见下)
- 修复版本:本地 staging 候选(未 push / deploy
### 旧防线为何没拦住(hasRectificationSession + sessions.find()
- `hasRectificationSession` 只断言“存在任意校正 Session”,不区分 draft / collecting / candidate_ready / candidate_accepted / confirmed / closed,因此已完成会话始终被当作可继续,且没有服务端 Case 状态可被测试断言。
- `sessions.find(sessionType === "birth_time_rectification")` 取客户端数组第一条,既不保证精确 sessionId,也不校验 Case 绑定;排序、缓存或刷新差异都会改变打开哪条记录。
- 旧测试只覆盖“能找到一条校正 Session”的客户端行为,没有服务端 Case 状态机、没有“点击指定 Session 必须精确恢复”的契约、没有并发/双击/多标签幂等断言,也没有 legacy→V9 一次性回填的数据库级验证,因此这些缺陷在回归中被遗漏。