docs(rectification): add billing failure and question-slot task brief
Traces the staging run_failed on the first user message to a missing feature_pricing row: the opening turn is free and skips reserve(), so the first `message` action is the first request to hit resolve_feature_pricing, which raises feature_pricing_missing and maps to the generic run_failed fallback. Also records the independent opening-turn defect that leaves current_question null. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LVapmh2oGNyr6ECHKjPJY8
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
# 任务书 · 生时校正 run_failed 与开场轮问题槽为空(2026-08-31)
|
||||
|
||||
基线:`origin/staging` @ `9492bae1`("docs: record round-1 audit and open round-2 brief")。
|
||||
|
||||
本轮两条必做任务来自一次线上故障的代码溯源,根因已定位到具体行,**不需要重新排查**。第三条是配置缺口,只出方案不动手。
|
||||
|
||||
---
|
||||
|
||||
## 现象
|
||||
|
||||
用户开始生时校正,助手正常提出第一个问题;用户回答第一条自然语言证据("2016 年 9 月上大学")后,前端立刻返回:
|
||||
|
||||
```json
|
||||
{"type":"error","code":"run_failed","message":"生时校正暂时不可用,请稍后再试。"}
|
||||
```
|
||||
|
||||
同时问题槽显示"当前没有可回答的问题,正在等待服务端更新。"
|
||||
|
||||
案例 `bf7dafa9-6c12-424f-b21f-625d5497cbf2` 快照特征:`case_revision: 0`、`turns` 只有 1 条、`last_activity_at` 仍等于第 1 轮的 `created_at`、`current_question: null`、`evidence: []`。说明第 2 轮在 `append_agentic_rectification_turn` 落库之前就抛错了。
|
||||
|
||||
## 这不是逻辑回归
|
||||
|
||||
已实测:`./node_modules/.bin/tsc --noEmit` exit 0(TypeScript 5.9.3);`npx tsx --test tests/rectification-*.test.ts` 705 条全绿(696 pass / 9 skipped / 0 fail / 0 error)。最近的 `3a4396a4`(stop 语义)与本次故障无关,**不要去动它**。
|
||||
|
||||
---
|
||||
|
||||
## 硬红线
|
||||
|
||||
1. **不得修改 `feature_pricing` 的 SQL 函数签名或校验逻辑**(`supabase/migrations/20260831010000_feature_pricing.sql`)。`resolve_feature_pricing` 抛 `feature_pricing_missing` 是**正确行为** —— 缺定价就该拒绝计费,不能默默按 0 或默认价放行。本轮修的是错误如何被映射和呈现,不是要绕过这个闸门。
|
||||
2. **不得在 migration 里硬编码价格 seed。** 定价是 admin 可配置的业务数据(`admin_save_feature_pricing_draft` / `admin_publish_feature_pricing`),写死会和发布流冲突,并且会让"生产环境用了测试价"这类事故无声发生。
|
||||
3. **不得修改 `src/lib/rectification-agentic/core/rectification-decision.ts`** 及其 stop 语义相关逻辑。那是 `3a4396a4` 刚落地的,与本次故障无关。
|
||||
4. **不得把内部错误原文透给用户。** `consult-safety.ts` 的既有约束继续生效。任务 A 要让错误**可诊断**(服务端日志)且**可行动**(用户文案),这是两件事,不要混为一谈。
|
||||
5. **不得修改既有测试断言** —— 除非该断言锁住的正是本轮要修的缺陷本身;那种情况必须在断言上方注明原值与原因,并在 PROGRESS 里单列。
|
||||
6. 不得手写 `useCallback` / `useMemo`。
|
||||
7. 推 staging 前必须 `./node_modules/.bin/tsc --noEmit` 通过。**不要用 `npx tsc`** —— 新建 worktree 里 `node_modules` 未安装时 npx 会去装空包 `tsc@2.0.4`(本仓库既有教训)。
|
||||
8. `tests/rectification-*.test.ts` 不得低于基线 **705**,且 `fail=0`。全量 `npm test` 需要 Docker,本机无 Docker 时会有既有的数据库/部署类失败 —— **必须逐条比对失败清单,确认没有新增**。
|
||||
9. 不得改 `.gitea/workflows/**`。不得在有未提交改动的工作树上切分支。不得自行把 staging 提升到 main。
|
||||
|
||||
让步顺序:计费正确性 > 功能与测试不回归 > 错误可诊断 > 用户文案 > 代码整洁。
|
||||
|
||||
## 开工前置
|
||||
|
||||
```bash
|
||||
git fetch origin --prune
|
||||
git worktree add -b codex/rectification-billing-20260831 \
|
||||
../.worktrees/rectification-billing-20260831 origin/staging
|
||||
```
|
||||
|
||||
基线必须是 `origin/staging`,不是任何本地 ref。读 `docs/research/pre_work_error_ledger.md`,跑 `scripts/pre_work_check.py`,读 `frontend/AGENTS.md`(Next.js 版本与训练数据不同,写代码前先看 `node_modules/next/dist/docs/`)。改前先在 `docs/BUG_HISTORY.md` 检索同类记录。
|
||||
|
||||
**下面所有行号只是线索,请按符号名定位**,`origin/staging` 上的行号可能有偏移。
|
||||
|
||||
---
|
||||
|
||||
## 任务 A(P0)· `billing_unavailable` 没有用户可见映射
|
||||
|
||||
### 事实(已逐行确认)
|
||||
|
||||
今天的提交 `aaaa5fb7 feat(billing): add feature pricing configuration`(2026-08-31 03:37)在每个付费校正轮前插入了定价查询。故障发生在 05:44。完整链路:
|
||||
|
||||
1. `src/app/api/rectification/agent/route.ts:567` — `billing.reserve()` 调 `resolveFeaturePricing(accounting, "rectification", selectedModel.id)`
|
||||
2. `supabase/migrations/20260831010000_feature_pricing.sql:69` — 无 `('rectification', <model_tier>)` 且 `status='published' and enabled` 的行时抛 `feature_pricing_missing`
|
||||
3. **该 migration 建的是空表**,全仓库无任何 seed insert,行只能由 admin 发布流产生 → staging 上就是没有
|
||||
4. `src/lib/feature-pricing.ts:46` 包装为 `FeaturePricingError`;`reserve()` 捕获后返回 `reason: "billing_unavailable"`(route.ts:583)
|
||||
5. `src/lib/rectification-agentic/v9/agent-run.ts:263` — `throw new RectificationToolServiceError(reserve.reason ?? "billing_denied")`
|
||||
6. route.ts:702-721 的 catch 映射链**没有 `billing_unavailable` 分支**,逐个 else-if 落空后进兜底 `run_failed`
|
||||
|
||||
第 1 轮能成功、第 2 轮才炸,是因为 `reserve()` 开头有 `if (action !== "message") return { success: true, status: 200 }` —— `opening` 轮免费、根本不查定价。用户的第一条消息是第一个 `message` 动作,也就是第一个撞上定价查询的请求。这与"`turns` 只有 1 条 + `case_revision: 0`"完全吻合。
|
||||
|
||||
### 要做的
|
||||
|
||||
**A-1 补映射。** 在 route.ts 的 catch 映射块里为 `billing_unavailable` 加独立分支,与既有 `billing_denied` 并列。配置缺失**不可自愈**,文案不能是"请稍后再试"(用户重试一万次也不会好),要引导联系支持 —— 参考同一块里 `skill_identity_missing` 分支的语气。
|
||||
|
||||
**A-2 让日志可诊断。** `reserve()` 的 catch(route.ts:581-584)当前只记 `error.name`,日志里只能看到 `FeaturePricingError`,把 `feature_pricing_missing`(没配价)和 `feature_pricing_model_unavailable`(模型没发布版本)这两种**排查动作完全不同**的情况吞掉了。`FeaturePricingError` 在 `src/lib/feature-pricing.ts:19` 已带 `code` 字段,改成记录它,并让返回的 `reason` 能区分这两种。用户侧文案不必区分,服务端日志必须区分。
|
||||
|
||||
**A-3 同类暴露面。** 已确认以下两处有同样问题,一并按一致方式修,**但不要顺手重构这两个路由的其他部分**:
|
||||
|
||||
| 位置 | 现状 |
|
||||
| --- | --- |
|
||||
| `src/app/api/consult/route.ts:375` | 直接调 `resolveFeaturePricing(accounting, "chat.standard", model.id)`,外层只判断 `error_code === "insufficient_credits"`,`FeaturePricingError` 会穿透到通用 catch |
|
||||
| `src/app/api/reports/route.ts:202` | `billing.reserve` 里直接调 `resolveFeaturePricing(admin, "report.full", modelId)`,无任何针对性映射 |
|
||||
|
||||
---
|
||||
|
||||
## 任务 B(P0)· 开场轮不持久化问题槽
|
||||
|
||||
### 事实
|
||||
|
||||
`current_question` 为 null,导致 `src/components/rectification-agentic-chat.tsx:1084` 的 `showMissingQuestion` 为真,用户看到"当前没有可回答的问题,正在等待服务端更新。"
|
||||
|
||||
原因:`persistNextInterviewIfIdle`(route.ts:677)被包在 `if (action === "message")` 里,`opening` 轮不会执行。开场轮的工具回执佐证了这点 —— 只跑了 `rectification-read-case`,没有任何能生成 `collect_spoken` focus 的工具,助手的问题只以散文形式出现在回复正文里。
|
||||
|
||||
`action` 枚举见 route.ts:69:`["opening", "message", "read_only", "answer_choice", "stop_and_review"]`。
|
||||
|
||||
**这是独立于计费的缺陷。任务 A 修完、定价行发布之后,它依然存在。**
|
||||
|
||||
### 决策点 —— 动手前先在 PROGRESS 里写明你选哪个及理由
|
||||
|
||||
- **方案 1(建议)**:让 `opening` 轮成功后也走 `persistNextInterviewIfIdle`。理由:这条路径是服务端确定性的,不依赖模型是否想起来调工具;且 `ensureNonTerminalTurnExit` 已是同一分支里的同类先例。
|
||||
- **方案 2**:让 agent 在开场轮自行调用持久化 focus 的工具,需改 skill prompt(`jyotish-birth-time-rectification@10.0.13`)。注意 skill 包有 sha256 绑定校验(`agent-run.ts:252` `resolveExactSkillPackage`),改 prompt 意味着要发新版本并处理已有 case 的 `skill_identity_mismatch`,成本显著更高。
|
||||
|
||||
若选方案 1,`ensureNonTerminalTurnExit` 当前与 `persistNextInterviewIfIdle` 在同一个 `action === "message"` 分支内 —— **请单独判断它是否也该放开,不要默认捆绑修改**,并把判断写进 PROGRESS。
|
||||
|
||||
---
|
||||
|
||||
## 任务 C(P1)· 定价数据缺口 —— 只出方案,不要实现
|
||||
|
||||
`feature_pricing` 表出厂为空。任何环境部署后若不手动发布定价行,`rectification`、`chat.standard`、`report.full` 三条付费链路**全部**会挂,且(在任务 A 修完前)都表现为语焉不详的通用错误。
|
||||
|
||||
请评估并**在 PR 描述里提出建议,本轮不要写实现**:
|
||||
|
||||
- 是否需要环境就绪检查,在定价行缺失时**早失败**并给出明确信息,而不是等用户点到第二轮才炸
|
||||
- 该检查放在哪里:`scripts/db-migrate.mjs` 之后的独立脚本?还是应用启动期?
|
||||
- 已发布的 `feature_key` 与 `model_tier` 组合应如何做覆盖度校验(三个 feature × 该环境所有已发布模型的 tier)
|
||||
|
||||
注意红线 2:不要用 seed migration 解决这个问题。
|
||||
|
||||
---
|
||||
|
||||
## 验收标准
|
||||
|
||||
1. `cd frontend && ./node_modules/.bin/tsc --noEmit` exit 0
|
||||
2. `cd frontend && npx tsx --test tests/rectification-*.test.ts` 不低于 705 条且 `fail=0`
|
||||
3. 任务 A 补测试:断言 `billing_unavailable` 不再落到 `run_failed` 兜底,且 `feature_pricing_missing` / `feature_pricing_model_unavailable` 在服务端日志里可区分
|
||||
4. 任务 B 补测试:断言 `opening` 轮结束后 `current_question` 非空
|
||||
5. 任务 C 的方案写在 PR 描述里,仓库内无对应实现改动
|
||||
|
||||
## 交付前必须说明
|
||||
|
||||
本任务书的根因链是**静态溯源**得出的,作者没有 staging 凭据(仓库内无 `.env`),**未在真实环境验证**。你若同样无凭据,不要声称已验证。staging 侧仍需人工确认两件事:
|
||||
|
||||
```sql
|
||||
select feature_key, model_tier, credit_cost, version, status, enabled
|
||||
from public.feature_pricing where feature_key = 'rectification';
|
||||
-- 预期:零行
|
||||
```
|
||||
|
||||
日志中应能找到:
|
||||
|
||||
```
|
||||
[rectification-v9] reserve failed case=bf7dafa9-6c12-424f-b21f-625d5497cbf2 reason=FeaturePricingError
|
||||
```
|
||||
|
||||
**若这两条都不成立,说明根因判断有误 —— 停下来登记 `BLOCKED.md`,不要硬改。**
|
||||
|
||||
修复上线后,运维侧还需通过 admin 流为 `rectification` 发布对应 tier 的定价行(`src/app/api/admin/feature-pricing/route.ts`)。**这是代码改动之外的必要动作,缺了它用户依然用不了** —— 请在 PR 描述里显式提醒。
|
||||
Reference in New Issue
Block a user