diff --git a/PROGRESS-billing-pricing-20260830.md b/PROGRESS-billing-pricing-20260830.md index 585da601..85adbb83 100644 --- a/PROGRESS-billing-pricing-20260830.md +++ b/PROGRESS-billing-pricing-20260830.md @@ -112,3 +112,31 @@ ### 全量测试补充 `./node_modules/.bin/tsx --test tests/*.test.ts`:2369 passed,1 failed。唯一失败为既有的 `tests/staging-backend-workflows.test.ts` YAML 语法检查,失败原因是运行环境的 Python 缺少 `yaml` 模块(`ModuleNotFoundError: No module named 'yaml'`);本轮未修改 `.gitea/workflows/**`,因此不是本轮回归。该环境缺口未通过新增依赖绕过。 + +--- + +## 外部核对(2026-08-31,按代码逐条验证,不依据本文件自述) + +| 任务 | 状态 | 验证依据 | +| --- | --- | --- | +| 0 用量成本聚合 | 已完成 | `frontend/src/app/api/admin/usage/aggregate/route.ts` | +| 1 报告接计费 | 已完成 | `api/reports/route.ts:203` authorize;`lib/personal-report-worker.ts:211,219` complete / release | +| 2 校正按功能定价 | 已完成 | `api/rectification/agent/route.ts:573` → `creditCost: pricing.credit_cost`,已脱离 `model.creditCost` | +| 3 feature_pricing 可配置 | 已完成 | `supabase/migrations/20260831010000_feature_pricing.sql` + `admin/feature-pricing` | +| 4 上下文缓存 | **部分完成** | 见下 | +| 5 会员参数 | **仅语义** | 见下 | +| 6 定价测算页 | 已完成 | `admin/pricing-simulator` + `lib/pricing-simulation.ts` + 单测 | + +### 任务 4 的缺口 + +`lib/agent-generation-settings.ts` 的 `cachedSystemMessage()` 对非 Anthropic provider 直接返回 `null`。用量解析(`readTokens` / `writeTokens` / `hit`)覆盖了全部 provider,但**真正产生降本的缓存标记只对 Anthropic 生效**。 + +同一文件顶部注释与 `api/daily-starlanguage/route.ts:105`(`deepseek-v4-flash`)都指向 DeepSeek 系。若生产 provider 非 Anthropic,本任务目前交付的是**可观测性,不是降本**。会员「随便聊」档的毛利模型依赖这项降本,因此这是阻塞项。**下一轮任务 1 处理。** + +### 任务 5 的缺口与判断 + +`product_entitlements` 的公平使用参数一个未改,仍为种子值:月卡 `billingLimit 2000`、年卡 `24000`、Pro 月卡 `5000`、Pro 年卡 `60000`(`20260806020000_billing_products_subscriptions.sql:146-155`)。 + +**这一保留是正确的**,符合任务书红线「不得凭估算改价」。缺的是上游输入:聚合端点已就绪但 staging 尚无真实单位成本数据。**这是流程阻塞,不是实现缺陷。下一轮任务 0 处理。** + +同时记录一个尚未解决的商业事实:在参数未调整前,月卡按 `billingLimit` 打满的成本高于售价,「随便聊」档在上线前必须先完成任务 0 与任务 1。 diff --git a/PROGRESS-rectification-convergence-20260830.md b/PROGRESS-rectification-convergence-20260830.md index c84438b9..247f34a9 100644 --- a/PROGRESS-rectification-convergence-20260830.md +++ b/PROGRESS-rectification-convergence-20260830.md @@ -96,3 +96,25 @@ - 任务 6 已完成实现与本地强制门禁,等待精确提交。 - 提交前及 push 前均重新同步并核对 `origin/staging`;若远端前进,只安全合并,不 reset/stash/clean。 - 任务 6 将单独快进推送到 `staging`;不手动部署,不提升 `main`。 + +--- + +## 外部核对(2026-08-31,按代码逐条验证,不依据本文件自述) + +| 任务 | 状态 | 验证依据 | +| --- | --- | --- | +| 0 上游手册与阈值 | 已完成 | `references/upstream/` 五份归档 | +| 1 轮次预算 | 已完成 | `core/rectification-decision.ts:250` `budgetExhausted()`,:263 走 `"exhausted"` | +| 2 问题槽 + 删清洗正则 | 已完成 | `v9/spoken-answer.ts` 已整文件删除,101 条正则不再存在 | +| 3 停止话术契约 | 已完成 | `scripts/rectification/api_service.py:6,200,214,240` 已恢复并接出 `next_step_codes` | +| 6 证据型停止语义 | 已完成 | `core/rectification-decision.ts:114` `evidenceStopReason()`,:148 参与判定 | +| 4 标定数据入口 | 未开始 | — | +| 5 清理与同步机制 | 未开始 | P2,按任务书本就应等任务 1–3 稳定两周 | +| **7 逐条回答归因表** | **未开始** | **P0,且不依赖任何外部数据,应优先于 8/9/10** | +| 8 质疑通道 | 未开始 | — | +| 9 改窗口重跑 | 未开始 | — | +| 10 首轮批量出题 | 未开始 | `src/mastra/agentic-rectification.ts` 中「一次一问」仍在 | + +任务 1、2、3、6 的实现与任务书要求一致,未发现踩红线:`convergence-evaluator.ts` 未被改动,`confirmation-gate.ts` 的 blocker 未放宽,sealed holdout 阈值未动,本仓仍未被 1993 案例答案污染(全仓 `pl9_1993` / `target_minute` 无相关命中)。 + +未完成项转入 `TASK-round2-cost-and-delivery-20260831.md`。 diff --git a/TASK-round2-cost-and-delivery-20260831.md b/TASK-round2-cost-and-delivery-20260831.md new file mode 100644 index 00000000..5eb3853f --- /dev/null +++ b/TASK-round2-cost-and-delivery-20260831.md @@ -0,0 +1,218 @@ +# 任务书 · 第二轮:真实成本与交付物(2026-08-31) + +基线:`origin/staging` @ `3a4396a4`。 + +**下面所有行号只是线索,请按选择器/函数名定位。** + +## 为什么要做 + +前两份任务书(`TASK-billing-pricing-20260830.md`、`TASK-rectification-convergence-20260830.md`)已完成大部分实现。按代码逐条核对后,剩余缺口集中在两类: + +**A 类 · 卡在缺真实数据,不是实现问题。** 计费任务 0 的聚合端点已就绪,但 staging 没有跑出真实单位成本,于是会员参数不敢动、缓存降幅无法验证、定价测算页只能显示估算。上一轮拒绝凭估算改参数是对的,**本轮负责把这个输入补上**。 + +**B 类 · 交付物本身还是空的。** 校正任务 7(逐条回答 → 区间收窄归因表)是 P0,且不依赖任何外部数据,但排在 8/9/10 后面一起没做。用户为一次校正付的钱,买的就是这张表。 + +还有一个上一轮遗留的阻塞项:**上下文缓存目前只对 Anthropic 生效**,若生产 provider 不是 Anthropic,则「随便聊」会员档的毛利模型不成立。 + +### 已完成,不要重做 + +计费任务 0/1/2/3/6,校正任务 0/1/2/3/6。核对依据见两份 PROGRESS 末尾的「外部核对(2026-08-31)」。 + +--- + +## 硬红线 + +1. **任务 0 是门控。** 会员参数、售价、公平使用上限的任何调整,必须在任务 0 产出真实单位成本之后。**不得凭估算改数**——上一轮正确地拒绝了这件事,本轮不得倒退。 +2. **不得用生产用户数据做压测。** 任务 0 的数据采集用真实调用,但必须是可控账号,且不得为了凑数据而批量刷接口造成成本与容量浪费。 +3. **归因表的收窄贡献必须来自服务端打分,不得由模型叙述生成。** 一张看起来专业、实则模型编的归因表比没有更糟。 +4. **零贡献证据必须显式标注**,不得静默丢弃。这是诚实性的一部分。 +5. **禁止答案回流**(沿用上一轮红线 3):任何以已知答案调出来的规则不得进入生产打分链路。 +6. **上游同步必须过污染审查**(沿用上一轮红线 4):不得同步带 `pl9_1993`、`observed_*_case_only`、`target_minute` 标记的打分逻辑。 +7. **诚实性不可让渡**:不得放宽 `confirmation-gate.ts` 的 blocker,不得降低 sealed holdout 门槛。 +8. **不得修改既有测试断言** —— 除非该断言锁的正是本轮要改的缺陷本身;那种情况必须在断言上方注明原值与原因,并在 PROGRESS 单列。 +9. 推 staging 前必须 `./node_modules/.bin/tsc --noEmit` 通过。**不要用 `npx tsc`**,本仓库环境下会装到空包 `tsc@2.0.4`。 +10. **数据库测试必须真跑。** `npm run test:db` 需要 Docker。没有 Docker 就不要推 —— 写进 `BLOCKED.md` 并停下。 +11. 不得改 `.gitea/workflows/**`。不得在有未提交改动的工作树上切分支。不得自行把 staging 提升到 main。 + +让步顺序:**诚实性不回退 > 数据不损坏 > 功能与测试不回归 > 可验证的改进 > 成本 > 代码整洁**。 + +## 开工前置 + +```bash +git fetch origin --prune +git worktree add -b codex/round2-cost-delivery-20260831 \ + ../.worktrees/round2-cost-delivery-20260831 origin/staging +``` + +读 `pre_work_error_ledger.md`,跑 `scripts/pre_work_check.py`,读 `frontend/AGENTS.md`。改前在 `docs/BUG_HISTORY.md` 检索同类记录。 + +**先读这些:** + +- 两份 PROGRESS 末尾的「外部核对(2026-08-31)」——本轮范围的依据 +- `frontend/src/app/api/admin/usage/aggregate/route.ts` —— 任务 0 的现成入口 +- `frontend/src/lib/agent-generation-settings.ts` —— `cachedSystemMessage()` 的 provider 分支 +- `frontend/src/lib/rectification-agentic/v9/evidence-model.ts` 与 `core/rectification-decision.ts` —— 任务 2 要接归因的地方 +- `references/upstream/1993-session-correction-rerun.txt` —— 归因表的目标形态就在这份记录里 + +--- + +## 任务 0(P0,门控)· 在 staging 采到真实单位成本 + +### 事实 + +计费任务 0 已交付聚合端点,但 `report.full` 从未有过真实行(上一轮才接上计费),`chat.standard` 与 `rectification` 也缺分叉后的新数据。没有这批数据,任务 3(会员参数)与任务 1 的降幅验证都无法进行。 + +### 要做什么 + +1. 把当前 staging 部署起来,确认 `/api/health` 的 SHA 与 `origin/staging` 一致,并在 PROGRESS 记录。 +2. 用可控账号跑出每类功能的真实调用,**每类至少 5 次、覆盖典型与偏长两种形态**: + - `chat.standard`:含多领域计算的完整咨询 + - `rectification`:至少一个跑到证据停止或预算耗尽的完整 case + - `report.full`:至少一份分章节完整生成 +3. 用聚合端点导出 per-feature 的 `runs / p50 / p95 / max` 的 `cost_microusd`、`input_tokens`、`output_tokens`、`duration_ms`,以及 `metadata.cache` 的命中情况。 +4. 结果写进 PROGRESS,形成一张表:**功能 → p50 成本 → p95 成本 → 缓存命中率**。 + +### 验收 + +- 三类功能各有不少于 5 条真实 `usage_ledger` 记录。 +- PROGRESS 里有上述表格,数值可复核到 request_id(脱敏,只列聚合值)。 +- 明确记录当前生产/staging 实际使用的 provider 与 model_id —— **任务 1 依赖这个结论**。 + +### 停止条件 + +若 staging 无法部署或无法产生真实调用,写进 `BLOCKED.md` 并停下。**任务 2 不受影响,可并行开工**;任务 1、3 必须等本任务。 + +--- + +## 任务 1(P0,依赖任务 0 的 provider 结论)· 让上下文缓存对生产 provider 真正生效 + +### 事实 + +`frontend/src/lib/agent-generation-settings.ts`: + +```ts +/** Adds only Anthropic's message-level cache marker; other providers keep current behavior. */ +export function cachedSystemMessage(content: string, model?: unknown) { + if (modelProviderId(model) !== "anthropic") return null; + ... +} +``` + +用量解析覆盖全 provider,但缓存标记只对 Anthropic 下发。同文件顶部注释与 `api/daily-starlanguage/route.ts:105`(`deepseek-v4-flash`)都指向 DeepSeek 系。 + +会员「随便聊」档的毛利模型完全依赖这项降本,因此这是上线阻塞项。 + +### 要做什么 + +1. 按任务 0 确认的生产 provider,查清其上下文缓存契约——**以 provider 官方文档为准,不要凭记忆写**。要弄清:怎么声明、命中如何计价、最小可缓存长度、前缀稳定性要求。 +2. 为该 provider 实现缓存声明,保证 system + skill 前缀落在缓存边界内。校正的 system message 内嵌绑定 Skill 全文(`v9/agent-run.ts` 的 `bootstrap`),是最值得缓存的稳定前缀。 +3. 不支持缓存的 provider 保持现有行为,返回 `null`,不报错、不改变请求形状。 +4. **降幅必须实测**:用任务 0 的同一批 case 形态再跑一轮,对比 p50 `cost_microusd`。没有前后对比就不许在 PROGRESS 声称降本。 + +### 验收 + +- 生产 provider 的请求能在账本 `metadata.cache` 看到非零命中。 +- 对话与校正的 p50 `cost_microusd` 相对任务 0 基线有可测量下降,降幅写进 PROGRESS。 +- 非目标 provider 行为不变,既有测试全绿。 + +--- + +## 任务 2(P0,无依赖,可立即开工)· 逐条回答 → 区间收窄归因表 + +> 这是 `TASK-rectification-convergence-20260830.md` 的任务 7,原文保留有效,本节为可独立执行的展开。 + +### 事实 + +`references/upstream/1993-session-correction-rerun.txt` 里,整场真实会话最有价值的产物是用户主动索要的那张表: + +| 你的回答 | 压缩后的分钟区间 | 作用 | +| --- | --- | --- | +| 2017 入职,岗位内容不合 | 14:48–14:50 | 很强的收窄点 | +| 2023 末起量,一改风格就稳 | 14:48–14:50 | 最接近最终分钟的一条 | + +本仓没有这个概念:`v9/evidence-model.ts` 与 `core/*.ts` 中不存在 `narrow` / `contribution` / `attribution` 语义,只有「这条证据支持哪个候选」,没有「这条回答把区间从 X 压到了 Y」。 + +同一份记录也证明了产品价值的真实边界:用户的 8 条生平锚点把窗口从 30 分钟压到 3 分钟,**收窄是真的**。归因表就是把这件事变得可见。 + +### 要做什么 + +1. 每次证据写入后记录**区间快照**:写入前宽度、写入后宽度、该条证据的归属贡献。**持久化**,不靠事后重算(重算会因候选集变化而不可复现)。沿用既有 receipt / SQL 函数模式,状态留在 Postgres。 +2. 报告(校正任务 3 的产物)新增归因表:用户原话(走 `display_date_label` 口径)→ 收窄前后区间 → 一句作用说明。 +3. 收窄贡献由服务端打分产出,**模型只做措辞**(红线 3)。 +4. 无贡献的证据也要列出并标注「未产生收窄」(红线 4)。 +5. 归因表要能回答用户那句真实提问:**「这个结论有多少来自我的回答?」** ——若最终区间主要由声明窗口或先验决定,必须说出来。 + +### 验收 + +- 报告含归因表,条目数等于已确认证据数。 +- 每条的收窄前后宽度与该轮实际候选集一致,可复核。 +- 零贡献证据被显式标注,不被静默丢弃。 +- 归因数据来自持久化快照,重开页面结果一致。 + +--- + +## 任务 3(P1,依赖任务 0 与任务 1)· 会员参数与售价重设 + +> 这是 `TASK-billing-pricing-20260830.md` 的任务 5,上一轮正确地保留未改,本轮在有数据后执行。 + +### 事实 + +`product_entitlements` 的公平使用参数仍为种子值(`20260806020000_billing_products_subscriptions.sql:146-155`):月卡 `billingLimit 2000`、年卡 `24000`、Pro 月卡 `5000`、Pro 年卡 `60000`。上一轮已完成语义澄清与文案约束,只差参数本身。 + +### 要做什么 + +1. 用任务 0 / 任务 1 的实测 p50、p95 成本重设 `minuteLimit` / `dayLimit` / `billingLimit`。**按 p95 定,不按均值**——公平使用要挡的是尾部。 +2. `billingLimit` 的定位是**熔断**,不是对用户承诺的配额;「随便聊」靠 `minuteLimit` + `dayLimit` 保护真人容量。语义已在上一轮写入 admin 文案,本轮只改数。 +3. 按产品已定规则,移除 standard 月卡/年卡的 `rectification` 与 `report.full` 权益(改为单独计费)。**移除前必须核查存量订阅依赖**,有在期会员依赖这些配额时给出兼容处理,不得让权益凭空消失。 +4. 售价(`billing_products.price_cents`)改动需产品确认后再执行,本任务只准备迁移,不擅自调价。 +5. 全部走幂等迁移或既有 `admin_save_product_draft` 审计路径,**不得手改生产库**。 + +### 验收 + +- 迁移幂等,`npm run db:migrate:check` 在隔离 Docker PostgreSQL 里 apply / check / reapply 三次退出码均为 0。 +- 新参数下,按 p95 成本估算的会员毛利写进 PROGRESS。 +- 存量订阅的兼容路径有明确记录。 +- 定价测算页读到新参数后,输出表与 PROGRESS 的数字一致。 + +--- + +## 任务 4(P1)· 「为什么是这个结论」的质疑通道 + +> `TASK-rectification-convergence-20260830.md` 的任务 8,原文有效。 + +真实会话里用户问「你是信息记忆联想了吗?还是靠印度占星专业技术推理出来的?」——这一问是全部真相的来源。本仓没有任何「为什么」入口(`v9/choice-card.ts` 的 `why` 是每个选项的说明,不是结论来源)。 + +内容全部来自服务端已有的 `claimCards` / `evidenceRefs` / `confirmation-gate` blocker,**不得由模型即兴生成**。与任务 2 的归因表共用同一份服务端数据,不要建第二套。 + +--- + +## 任务 5(P1)· 中途改窗口与保留证据重跑 + +> `TASK-rectification-convergence-20260830.md` 的任务 9,原文有效。 + +要点重申:同一 case 内重跑**不重复扣费**(沿用 `rectification:case:` 幂等键);重跑必须留痕;新窗口与既有证据冲突时如实呈现,不得静默丢弃。 + +--- + +## 任务 6(P1)· 首轮批量出题 + +> `TASK-rectification-convergence-20260830.md` 的任务 10,原文有效。 + +`src/mastra/agentic-rectification.ts` 中「一次一问」仍在。上游手册的节奏是第 1 轮 3–5 道 A/B/C/D 一起出。批量轮在轮次预算里**记为一轮**。 + +--- + +## 建议执行顺序 + +1. **任务 2** —— 无依赖,交付物核心,立即开工 +2. **任务 0** —— 与任务 2 并行,它是任务 1、3 的输入 +3. **任务 1** —— 拿到 provider 结论后立即做,它是「随便聊」上线的阻塞项 +4. **任务 3** —— 有成本数据后执行 +5. 任务 4 / 5 / 6 —— 依次 + +## 交付 + +- PROGRESS 写进 `PROGRESS-round2-cost-delivery-20260831.md`,任务 0 的成本表与任务 1 的降幅对比单列。 +- 每个任务一个提交。 +- 推 staging 前:`./node_modules/.bin/tsc --noEmit` 通过、`npm run lint` 无新增 error、`npm run test:db` 在 Docker 下 fail=0、`npm run build` 通过。 +- 全量 `./node_modules/.bin/tsx --test tests/*.test.ts` 的失败清单与基线逐条比对,确认无新增。