产品 09-18 授权:范围用户必须能正常用产品。exact-timing 只记录不 拦(日期照出、按区间表述),guarantee 全模式退回重写,personal-chart 只在完全没有出生时间时拦。推翻「分钟敏感主题一律套精确时间守卫」的 旧红线(该红线曾让已校正用户问应期被挖成「[具体时间已省略]」)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
13 KiB
TASK · 三通道重做修复单(2026-09-18)
基线:
origin/staging@7ae3aa9a(含实现提交94c1e81f)。 上游任务书:docs/tasks/TASK-consult-three-channels-20260918.md;实现进度:docs/tasks/PROGRESS-consult-three-channels-20260918.md。 本单只修94c1e81f带进来的未通过项,不重开设计。 BUG 编号起点:开工时docs/BUG_HISTORY.md最大号为 BUG-944,本单占 BUG-945 ~ BUG-949。
0. 验收结论(2026-09-18,Claude 验收)
在 7ae3aa9a 的干净工作树上实测:
| 项 | 结果 |
|---|---|
tsc --noEmit |
0 错 ✅ |
npm run lint |
0 error(120 warning,与基线同量级)✅ |
npm test |
3484 tests / fail 43;基线 742ffbc6 为 3484 / fail 31 → 新增 12 条红,0 条转绿 ❌ |
| 测试总数 | 3484 = 3484,未下降 ✅ |
Python tests/test_birth_time_journey_contract.py、tests/test_api_server_growth_contract.py |
17 passed ✅(属 BUG-939/940 那两个提交) |
| staging 部署 | /api/health 的 deployment.gitCommit 仍是 742ffbc6;门禁第 299 行就是 npm test --prefix frontend,12 条红即镜像不发布 ❌ |
进度记录写的「consultation-agentic-runtime.test.ts 本机 Windows EPERM 无法加载,以 Linux 门禁为准」在 Linux 上已证伪:这 12 条不是环境问题,全部可稳定复现(单独跑该 5 个文件:95 tests / fail 12,与全量一致)。
1. BUG-945(P0)领域上限降到 2,且超限是 schema 拒绝而不是截断
实证(frontend/src/mastra/consultation-tools.ts,7ae3aa9a 行号):
:93CONSULTATION_DOMAIN_DURATION_MS由 21_000 改成 31_000。:95-99CONSULTATION_DOMAIN_WALL_CLOCK_MS = 110_000 - 45_000 = 65_000,MAX_CONSULTATION_DOMAINS = floor(65_000 / 31_000) = **2**(改前是 3)。:137工具入参domains: z.array(...).min(1).max(MAX_CONSULTATION_DOMAINS)。
直接实跑 createConsultationTools(...).execute({question, domains:["career","finance","home"]}):
calls []
{"error":true,"message":"Tool input validation failed for run-jyotish-consultation ...
- domains: Array must contain at most 2 element(s)"}
即:模型提三个领域 → 一个领域都不算,runWorkflow 零次调用。而同文件 :632 的工具描述同时对模型说「list every domain the question needs」「Do not drop a relevant domain to shorten the plan」「if the clock runs short the server executes the domains that fit and returns the rest in omitted_domains」——描述承诺截断,schema 执行拒绝,两者互相矛盾。:368-373 的 executableDomainPlan(真正的截断逻辑)因此在超限路径上永远走不到,成了死安全网。
这条与本次任务书的目标直接冲突:任务书要治的就是 empty_answer,而「三领域问题被 schema 挡掉」是又一条通向空回答的新路径。
要求:
domains入参上限与「执行能装下几个」解耦:schema 侧放宽到领域注册表允许的长度(或保留一个明显更宽的上限,如 6),超出部分由executableDomainPlan截断并进omitted_domains,不得再由 zod 拒绝整次调用。- 工具描述与实际行为对齐:描述里说的「装不下的进 omitted_domains」必须是真的。
- 验收标准:新增合同测试——请求 3 个及以上领域时
runWorkflow至少被调用 1 次、结果带非空omitted_domains、不出现Tool input validation failed。
2. BUG-946(P1)31s 常数换掉后,既有预算断言没跟着改,6 条测试红
frontend/tests/consultation-agentic-runtime.test.ts 仍写着 21s / cap=3 的口径:
assert.equal(MAX_CONSULTATION_DOMAINS, 3); // 实际 2
assert.ok(MAX_CONSULTATION_DOMAINS * 21_000 <= 65_000);
assert.deepEqual(executableDomainPlan([...5 个]), { domains: [...3 个], ... });
assert.equal(domainFitsRunBudget(42_000, 2), true);
红的 6 条:multi-domain plan canonicalizes...、the merged answer policy...(Cannot read properties of undefined (reading 'answer_policy'))、the identical natal projection is carried once...(reading 'map')、the domain cap is what the run budget can actually pay for(2 !== 3)、a plan larger than the cap cannot be expressed...(false !== true)、a plan that runs long stops early...([] vs ['career'])。
要求:BUG-945 修完后,按新口径重写这 6 条断言,每条按 §7.3 写「原值 / 新值 / 原因」三栏。不得用放宽断言(改成 assert.ok(cap >= 1) 之类)的方式让它变绿。
3. BUG-947(P1)校正流的思考旁白被新门吞掉
frontend/src/lib/rectification-agentic/v9/stream-mapping.ts:194 把 sanitizePublicThinkingText 换成了 acceptThinkStepText:
export function toPublicThinkingDelta(text: string) {
const cleaned = acceptThinkStepText(text); // think-step-gate.ts
if (!cleaned) return null;
return { type: "thinking.delta", text: cleaned };
}
frontend/src/lib/think-step-gate.ts:8-17 的门是给 Pass 2 的整条条目 设计的:length >= 8、必须含 。!?、必须含 CJK。但这里喂进去的是 provider 的 reasoning-delta 分片——分片既不成句也常常短于 8 字。测试 never publishes reasoning-delta to the browser 正是被这条打红:mapStreamChunkToThinking("先核对经历。") 返回 null(6 字 < 8)。
后果:校正流用户在计算期看到的「思考」行会大面积消失。任务书 §2.1 的设计是「不发 chunk、只发完整条目」,实现只做了后半句(换门),没做前半句(校正流仍然按 chunk 喂),于是从「删词」变成「整条丢」。
要求:二选一,在进度记录里写明选了哪条与理由——
- (a) 校正流也改成条目制:累积到句末标点再过门发一条;
- (b) 校正流保留分片通道,但给它一个分片级的门(只判 CJK / 是否含工具名等,不改写、不删词),不复用
acceptThinkStepText。
验收标准:frontend/tests/rectification-step-answer.test.ts 全绿,且新增一条断言证明连续分片最终能产出至少一条可见 thinking 行。
4. BUG-948(P1)三类检测器在生产路径上没人调用 —— 按模式分开处置
4a. 缺口实证
任务书 §3.5 的原话是「正则本身保留为检测器供 Pass 4 判定,但不得再写回文本」。实现只做了后半句:
frontend/src/lib/timing-output-guard.ts:147/152:guardPreciseTimingOutput/guardGeneralNoBirthTimeOutput变成恒等函数。frontend/src/lib/consultation-birth-time-mode.ts:85-95:createBirthTimeModeOutputGuard仍在调这两个恒等函数——整个出生时间模式的输出边界现在什么都不做。frontend/src/app/api/consult/route.ts:1233:Pass 4 只接了detectMethodologyBookkeeping。detectPreciseTimingViolations/detectGeneralNoBirthTimeViolations全仓无生产调用方。
红的 3 条测试:evidence-blocked unverified answers still use the deterministic timing guard、minute-sensitive or timing themes use the precise-timing output guard、general mode deterministically rejects personal chart claims while preserving general knowledge。
4b. 决策记录(产品负责人 2026-09-18 授权)
产品诉求原话:「即使用户知道一个出生范围,也能正常地用咱们的产品,而不是不能用。」
这条决策推翻旧的「分钟敏感主题一律套精确时间守卫」红线。旧红线的实证写在 frontend/tests/rectification-range-reading-20260906.test.ts:50-69:
const text = "Rahu 大运为 2013年11月21日 至 2031年11月22日。";
// verified_chart + currentTheme 是 marriage(分钟敏感)或 timing →
assert.match(marriage, /具体时间已省略/);
即已校正、已确认分钟的用户,只要问应期就被挖空。docs/BUG_HISTORY.md 第 3504 行记的就是这个事故(用户因此以为看精确日期要先付费做校正)。本单不恢复该行为,也不把它换成「退回重写」——要恢复的是「照实说,按区间说」。
三类检测器按保护对象分开处置:
| 检测器 | 适用模式 | 处置 | 理由 |
|---|---|---|---|
exact-timing(日期 / 月份) |
verified_chart、unverified_birth_time、declared_birth_window |
不拦、不改写,命中只记 pass4-observe 运行步 |
大运与行运边界是算出来的事实。范围用户该拿到的是「2013 年 11 月中下旬」这类区间说法,不是被删掉的半句话 |
guarantee(一定会 / 保证 / 注定 / will definitely) |
全部模式 | 拦:Pass 4 命中记 pass4-reject 并退回 Pass 3 重写一次;二次仍命中则该句不发,其余正文照常 |
与出生精度无关,是可信度红线;拦它不减少任何用户信息 |
personal-chart(「你的上升是巨蟹座」类) |
仅 general_no_birth_time |
拦:同上退回重写;二次仍命中用 GENERAL_NO_BIRTH_TIME_REFUSAL 兜底句替代整段(不得就地替换半句) |
此模式下连出生日期精度都没有,任何「你的盘」断言都是编造;同时挡住 fixture 里的提示注入例句 |
关键是第三行的「仅」:出生范围用户走的是 declared_birth_window(shouldRunDeclaredWindowWorkflow),不是 general_no_birth_time,因此这条拦截不会落到范围用户头上。窗口模式的读盘口径已经存在,沿用即可:ACCEPTED_RANGE_READING_INSTRUCTION——「区间里稳定的主题按代表分钟读;会随分钟变的主题按范围读,不得写成单一分钟结论」。
4c. 任务分解与验收标准
- 接线:
detectPreciseTimingViolations/detectGeneralNoBirthTimeViolations接进 Pass 4,按 4b 表分流到pass4-observe(只记)与pass4-reject(退回重写)。回执必须能区分这两者。- 验收:回执里出现
pass4-observe/pass4-reject两种运行步,且都带命中的kind。
- 验收:回执里出现
- 恒等壳下线:
guardPreciseTimingOutput/guardGeneralNoBirthTimeOutput两个@deprecated恒等函数与createBirthTimeModeOutputGuard的调用点一并删除,别留「看起来在保护、其实什么都不做」的假边界。- 验收:全仓 grep 不到这两个函数名。
- 范围用户合同测试(本单的产品验收点):新增断言——
declared_birth_window模式下,含具体日期的正文原样通过,不出现[具体时间已省略],且分钟敏感主题的回答里带区间说法。- 验收:这条测试在
git revert掉 4b 的改动后必须变红(证明它真的钉住了这个行为)。
- 验收:这条测试在
- 重写 3 条红测试:按新口径改断言,每条写「原值 / 新值 / 原因」三栏,其中
rectification-range-reading-20260906.test.ts:50-69的期望由「挖空」改为「日期原样保留」。不得删除测试。 - 文案同步:
CHANGELOG.md写明「出生范围用户的应期回答不再被删字,改为按区间表述」;若frontend/docs/VOICE.md有相关口径一并更新。
5. BUG-949(P2)容量算术两条红
frontend/tests/consultation-session-capacity.test.ts:
measured thinkingSections and receipts stay inside the physical-cap arithmetic:1-domain sections JSON length 1053,低于断言下限 1200(本命计划改成四标题后 thinking plan 变小了,是预期方向,但断言没跟上)。session-detail JSON at the old ~19-round cap versus the new ~50-round cap:19 full rounds already exceeded the old combined quota。
要求:按新的 thinking plan 形状重算这两条断言的上下界,写三栏说明;顺带确认 docs 里凡是引用「19 轮 / 50 轮」容量口径的地方是否需要同步。
6. 硬红线(照抄上游任务书,未变)
tsc --noEmit0 错、npm run lint0 error、npm test不得比基线742ffbc6的 31 条失败更多(目标是回到 31,即 12 条全绿)。- 测试总数不得低于 3484。
- 改任何既有断言写「原值 / 新值 / 原因」三栏;不得用弱化断言换绿。
- 不得再引入「先混流再过滤」;新增正则必须能回答「是门还是刀」。
- provider reasoning 不进任何对外事件、数据库可见字段、完整原文日志(
consultation-budget.ts:55的 80 字截断日志维持现状即可)。
7. 让步顺序
- 先修 BUG-945(P0,用户可感知的空回答路径),再修 946。
- BUG-947、948 可与 945 并行(文件不重叠:947 只碰
rectification-agentic/v9/stream-mapping.ts+think-step-gate.ts,948 只碰timing-output-guard.ts/consultation-birth-time-mode.ts/route.ts的 Pass 4 接线)。 - BUG-949 最后做,它依赖前面几条最终的 thinking plan 形状。
- 实在做不完,945 + 946 必须一起进,否则 staging 仍然发不出镜像。
8. 开工前置
git fetch origin --prune
git worktree add -b codex/consult-three-channels-fix-20260918 \
.worktrees/consult-three-channels-fix-20260918 origin/staging
cd .worktrees/consult-three-channels-fix-20260918/frontend
npm test 2>&1 | grep -E "^# (tests|pass|fail)" # 开工基线:tests 3484 / pass 3426 / fail 43
收工要求:docs/tasks/PROGRESS-consult-three-channels-fix-20260918.md + docs/BUG_HISTORY.md(BUG-945~949)+ CHANGELOG.md(若有用户可感知变化),与代码同一批推 staging。