Files
Jyotisha/docs/tasks/TASK-rectification-request-dossier-cache-20260915.md
T
Jesse_ChenandClaude Opus 5 e4f9f3ee0f docs(tasks): 校正链路审计四单(引擎记忆化 / 故障归因 / 渲染拆分 / 档案缓存)
只读审计 origin/staging @ 6b3248bf 后出的四份任务书:

- engine-memoization(BUG-721,纯 Python 可并行):一次重算 45% CPU 在重复
  算同一份 Shadbala,过境盘按候选算了 2196 次(应 36),鉴别探针算两遍,
  _cached_rows 是死代码。本机等价实验 3358 → 1604 ms,candidate_scores 与
  decision_receipt 逐字相同。只做记忆化,不改算法。
- failure-attribution(BUG-722/723/724,独占 route.ts):分类器失败被说成
  用户说不清且丢证据;引擎 429 被当成引擎坏、不重试不打日志(复发自
  BUG-715);attempt 210s × 2 > maxDuration 240s(复发自 BUG-059)。
- settled-render-split(BUG-725,前端可并行):校正会话流式每帧重渲整条
  对话并重跑已结算消息的 Markdown;BUG-473 的咨询面拆分没有跟过来。
- request-dossier-cache(BUG-726,串行在 failure-attribution 之后):一轮
  取 3.44 次整份 Case 档案,改成写即失效的请求作用域缓存,零调用点改动。

纯文档推送,不触发门禁、不发布镜像、不部署。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
2026-09-15 17:13:52 +00:00

132 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TASK · 一轮对话把整份 Case 档案从数据库取 3.4 次
- 日期:2026-09-15
- 基线 commit`origin/staging` @ `6b3248bf`
- 执行分支:`codex/rectification-request-dossier-cache-20260915`
- **串行在 `TASK-rectification-failure-attribution-20260915` 之后**:两单都要改 `frontend/src/app/api/rectification/agent/route.ts`,本单必须以那一单合入后的 staging 为基线,不得并行
- 主要落点:`frontend/src/lib/rectification-agentic/v9/tool-service.ts`(或新建同目录的缓存模块)、`route.ts` 一处接线
- 规模:一个请求内缓存层 + 一处接线。**零调用点改动、零行为变化。**
---
## 1. 事故实证
`loadV9CaseDossier` / `loadV9CaseCompute``tool-service.ts`)每次调用都是一次 Postgres RPC,没有任何缓存。全仓约 **40 个调用点**`rectification-v9-tools.ts` 13 + 3、`answer-choice.ts` 6 + 4、`block-scan-answer.ts` 3、`agent-run.ts` 3 + 1、`score-persist.ts` 2 + 1、`route.ts` 2 + 2,以及 `regenerate-turn.ts``refresh-discriminator-probes.ts`、三条 GET 路由各一。
用现有测试套件的 RPC 计数器实测(在 `fakeAccounting` 上按场景聚合):
| 路径 | 场景数 | `get_..._case_dossier` | 每场景均值 | `get_..._case_compute` | 均值 |
| --- | ---: | ---: | ---: | ---: | ---: |
| Agent 轮(`rectification-v9-stream.test.ts` | 39 | 134 | **3.44** | 25 | 0.64 |
| 点选题(`rectification-answer-choice.test.ts` | 15 | 31 | **2.07** | 17 | 1.13 |
同一轮里还有 `insert_agentic_rectification_run_phase` 7.05 次、`set_agentic_rectification_conversation_focus` 2.46 次——那些是真写入,不在本单范围。
这份档案不小。`get_agentic_rectification_case_dossier``supabase/migrations/20260907020000_rectification_declared_uncertainty.sql` 里的最新定义)单次要做:
-`agentic_rectification_cases` 一行
- 取**最近 50 轮** turns`cross join lateral` 展开成 user/assistant 两条消息再 `jsonb_agg`
- 取该 Case 的**全部** evidence 行 `jsonb_agg`
- 两次 `count(*)`evidence、turns
- 取 conversation summary(不存在时还要先 `refresh_..._conversation_summary`
- 取最新 result,并调 `compose_agentic_rectification_decision_receipt` 合成收据(内含 candidates 数组)
在 2 vCPU 的生产主机上,这段 jsonb 构建与 Next.js、Python 引擎抢的是同一批核。
界面侧还会放大:`questionGap === "preparing"``useVisibilityAwarePoll``RECTIFICATION_QUESTION_RETRY_INTERVAL_MS = 2000` 毫秒打一次 `GET /api/rectification/cases/[caseId]`,每次又是一份完整档案(有重试次数上限,不是无限轮询)。
## 2. 根因
档案在**一次请求内**是不变的(除非本请求自己写了东西),但每个需要它的地方都独立重查。没有请求作用域的概念,于是「读一次用多处」退化成「用几处读几次」。
## 3. 决策记录
产品 2026-09-15 授权本单,口径:
1. **缓存必须是「写即失效」的,不是「请求内固定」。** 档案在一次请求里**会**变——写证据、设焦点、落候选、切状态都会改它。任何「整个请求只读一次」的实现都会让下游拿到陈旧档案,那比多查几次严重得多。
2. **不得改动那 40 个调用点。** 逐点传缓存参数既大又容易漏一处,漏的那一处就是陈旧数据。缓存必须挂在**客户端对象**上,对调用点完全透明。
3. **不引入跨请求缓存。** 档案里有出生资料派生数据与会话内容,跨请求持有违反 AGENTS §8;而且并发校正之间必然串味。
4. **本单不动轮询间隔、不动 GET 路由的投影形状。** 「档案能不能瘦一点」「GET 能不能只返回下一问」是另一个话题,需要先确认哪些字段真的有人读,本单不碰。
## 4. 硬红线
1. 写操作后必须能读到写后的档案。这是本单唯一的成败判据。
2. 缓存生命周期严格等于一次 HTTP 请求(含流式响应体执行期间),不得挂在模块作用域、`globalThis` 或任何跨请求容器上。
3. 缓存键必须包含 `userId``caseId`。同一请求内不会出现第二个用户,但键里带上它是防止将来被误用的最低成本。
4. `route.ts` 只允许新增**一处**接线(包装 `createAdminSupabaseClient()` 的返回值)。不得在别处零散加缓存。
5. 不得改 `get_agentic_rectification_case_dossier` / `_compute` 的 SQL 定义(本轮不动数据库结构,AGENTS §7.6)。
6. 不得顺手升级依赖、不得顺手修不在本单里的 warning。
## 5. 任务分解
### 5.1 请求作用域的缓存包装器
新增 `withRectificationRequestCache(accounting)`,返回一个与 `RectificationRpcClient` 结构相同的包装对象:
- `rpc("get_agentic_rectification_case_dossier" | "get_agentic_rectification_case_compute", args)`:按 `(fn, p_user_id, p_case_id)` 命中则返回缓存的 in-flight promise,未命中则实际调用并缓存该 promise(缓存 promise 而不是结果,可以顺带把并发的重复读合并成一次)。
- `rpc(其它任何函数名, args)`:**先整体清空缓存**,再转发。不需要区分读写——除这两个只读投影外的 RPC 一律按可能写处理,这是保守且正确的一侧。
- 任何其它属性(例如某些调用点用 `accounting as never` 走的 `.from(table).update(...)`)必须透传,并且在被访问时同样清空缓存。用 `Proxy` 或显式转发都行,选一个并在注释里写明为什么。
注意:`agent-run.ts``case-service.ts``session.ts``regenerate-turn.ts` 里有若干 `accounting.rpc(...)` 的**直接**调用(不走 `tool-service.rpc` 这个私有 helper)。正因为如此,缓存必须挂在客户端对象上——挂在 `tool-service` 的 helper 里会漏掉这些。
- 验收:单元测试覆盖——连续两次读同一 `(userId, caseId)` 只产生一次底层 RPC;中间插入任意一次其它 RPC 后,第三次读必须重新打底层;两个不同 `caseId` 互不命中;并发两次读只打一次底层。
- 验收:`.from(...)` 透传后缓存被清空。
### 5.2 在路由入口接上
`route.ts``accounting = createAdminSupabaseClient()` 之后立刻包一层,后续一切原样。`regenerate` 路由与三条 `cases/[caseId]/*` GET/POST 路由同样处理(每条也只允许一处接线)。
- 验收:源码合同断言这几条路由里 `createAdminSupabaseClient()` 的返回值都经过包装器,没有裸用。
### 5.3 用现有套件量出前后差
把 §1 那张表的测法固化下来:在 `rectification-v9-test-support.ts``fakeAccounting` 上加一个可选的计数导出(**默认关闭**,靠显式开关启用,不得影响既有断言),新增一条测试断言「同一场景下 `get_..._case_dossier` 的调用次数不超过 N」。N 取改动后的实测值,不是拍的。
- 验收:进度记录里贴出改前/改后两组计数(Agent 轮与点选题各一组)。
- 验收:既有 34 + 47 条断言一条不改仍全绿。
### 5.4 Bug 历史
预占 **BUG-726**,状态可写 `resolved`(有针对性回归)。这不是回归,是自 V9 运行时引入以来的分层遗漏。防复发写成:**Case 只读投影必须经请求作用域缓存读取;新增只读投影要么进缓存白名单,要么在记录里写明为什么不能缓存。**
## 6. 让步顺序
1. 5.1 + 5.2 是一体,必须一起做。只做 5.1 不接线等于没做。
2. 5.3 的计数断言可以只覆盖 Agent 轮一条路径,点选题那条砍掉时在进度记录里写明。
3. 5.4 不得砍。
4. 如果 5.1 的「其它属性透传」在类型上过不去(`AccountingClient` 只声明了 `rpc`,多处调用点用 `as never` 绕过),**宁可缩小范围**:只包装 `rpc`,并在记录里写明 `.from(...)` 路径未覆盖、可能读到陈旧档案的具体位置。不得为了让类型通过去改业务代码(这是产品明确的偏好)。
## 7. 开工前置命令
```bash
git fetch origin --prune
# 确认 failure-attribution 单已合入 staging 再开工
git log --oneline origin/staging | head -5
git worktree add -b codex/rectification-request-dossier-cache-20260915 \
.worktrees/rectification-request-dossier-cache-20260915 origin/staging
cd .worktrees/rectification-request-dossier-cache-20260915/frontend
git status -sb | head -1
npm ci
```
验收命令:
```bash
./node_modules/.bin/tsc --noEmit
npm run lint
npx tsx --test tests/rectification-*.test.ts tests/agentic-rectification-*.test.ts
npx tsx --test tests/*.test.ts # 与基线逐条比对失败清单
npm run build
```
## 8. BUG 编号起点
基线 `6b3248bf` 上最大号 **BUG-720**。本单预占 **BUG-726**。因为串行在 failure-attribution722724)之后,开工时那几号大概率已落库;以当时实际最大号 +1 为准。
## 9. 不在本单范围
- 档案投影瘦身(哪些字段真的有人读、50 轮 turns 是不是必要)
- `useVisibilityAwarePoll` 的 2 秒间隔与重试上限
- `insert_agentic_rectification_run_phase`(每轮 7 次)的批量化
- 引擎耗时、前端渲染、故障归因(各有专单)