Files
Jyotisha/docs/tasks/TASK-consult-three-channels-fix-20260918.md
T
Jesse_ChenandClaude Opus 5 b4fcb9466f docs(tasks): BUG-948 改为按模式分开处置,钉住出生范围用户可用
产品 09-18 授权:范围用户必须能正常用产品。exact-timing 只记录不
拦(日期照出、按区间表述),guarantee 全模式退回重写,personal-chart
只在完全没有出生时间时拦。推翻「分钟敏感主题一律套精确时间守卫」的
旧红线(该红线曾让已校正用户问应期被挖成「[具体时间已省略]」)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
2026-09-18 04:36:32 +00:00

13 KiB
Raw Blame History

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-18Claude 验收)

7ae3aa9a 的干净工作树上实测:

结果
tsc --noEmit 0 错
npm run lint 0 error120 warning,与基线同量级)
npm test 3484 tests / fail 43;基线 742ffbc63484 / fail 31新增 12 条红,0 条转绿
测试总数 3484 = 3484,未下降
Python tests/test_birth_time_journey_contract.pytests/test_api_server_growth_contract.py 17 passed (属 BUG-939/940 那两个提交)
staging 部署 /api/healthdeployment.gitCommit 仍是 742ffbc6;门禁第 299 行就是 npm test --prefix frontend12 条红即镜像不发布

进度记录写的「consultation-agentic-runtime.test.ts 本机 Windows EPERM 无法加载,以 Linux 门禁为准」在 Linux 上已证伪:这 12 条不是环境问题,全部可稳定复现(单独跑该 5 个文件:95 tests / fail 12,与全量一致)。

1. BUG-945P0)领域上限降到 2,且超限是 schema 拒绝而不是截断

实证frontend/src/mastra/consultation-tools.ts7ae3aa9a 行号):

  • :93 CONSULTATION_DOMAIN_DURATION_MS 由 21_000 改成 31_000。
  • :95-99 CONSULTATION_DOMAIN_WALL_CLOCK_MS = 110_000 - 45_000 = 65_000MAX_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-373executableDomainPlan(真正的截断逻辑)因此在超限路径上永远走不到,成了死安全网。

这条与本次任务书的目标直接冲突:任务书要治的就是 empty_answer,而「三领域问题被 schema 挡掉」是又一条通向空回答的新路径。

要求

  1. domains 入参上限与「执行能装下几个」解耦:schema 侧放宽到领域注册表允许的长度(或保留一个明显更宽的上限,如 6),超出部分由 executableDomainPlan 截断并进 omitted_domains,不得再由 zod 拒绝整次调用。
  2. 工具描述与实际行为对齐:描述里说的「装不下的进 omitted_domains」必须是真的。
  3. 验收标准:新增合同测试——请求 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 for2 !== 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:194sanitizePublicThinkingText 换成了 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("先核对经历。") 返回 null6 字 < 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/152guardPreciseTimingOutput / guardGeneralNoBirthTimeOutput 变成恒等函数。
  • frontend/src/lib/consultation-birth-time-mode.ts:85-95createBirthTimeModeOutputGuard 仍在调这两个恒等函数——整个出生时间模式的输出边界现在什么都不做
  • frontend/src/app/api/consult/route.ts:1233Pass 4 只接了 detectMethodologyBookkeepingdetectPreciseTimingViolations / detectGeneralNoBirthTimeViolations 全仓无生产调用方。

红的 3 条测试:evidence-blocked unverified answers still use the deterministic timing guardminute-sensitive or timing themes use the precise-timing output guardgeneral 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_chartunverified_birth_timedeclared_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_windowshouldRunDeclaredWindowWorkflow),不是 general_no_birth_time,因此这条拦截不会落到范围用户头上。窗口模式的读盘口径已经存在,沿用即可:ACCEPTED_RANGE_READING_INSTRUCTION——「区间里稳定的主题按代表分钟读;会随分钟变的主题按范围读,不得写成单一分钟结论」。

4c. 任务分解与验收标准

  1. 接线detectPreciseTimingViolations / detectGeneralNoBirthTimeViolations 接进 Pass 4,按 4b 表分流到 pass4-observe(只记)与 pass4-reject(退回重写)。回执必须能区分这两者。
    • 验收:回执里出现 pass4-observe / pass4-reject 两种运行步,且都带命中的 kind
  2. 恒等壳下线guardPreciseTimingOutput / guardGeneralNoBirthTimeOutput 两个 @deprecated 恒等函数与 createBirthTimeModeOutputGuard 的调用点一并删除,别留「看起来在保护、其实什么都不做」的假边界。
    • 验收:全仓 grep 不到这两个函数名。
  3. 范围用户合同测试(本单的产品验收点):新增断言——declared_birth_window 模式下,含具体日期的正文原样通过,不出现 [具体时间已省略],且分钟敏感主题的回答里带区间说法。
    • 验收:这条测试在 git revert 掉 4b 的改动后必须变红(证明它真的钉住了这个行为)。
  4. 重写 3 条红测试:按新口径改断言,每条写「原值 / 新值 / 原因」三栏,其中 rectification-range-reading-20260906.test.ts:50-69 的期望由「挖空」改为「日期原样保留」。不得删除测试。
  5. 文案同步CHANGELOG.md 写明「出生范围用户的应期回答不再被删字,改为按区间表述」;若 frontend/docs/VOICE.md 有相关口径一并更新。

5. BUG-949P2)容量算术两条红

frontend/tests/consultation-session-capacity.test.ts

  • measured thinkingSections and receipts stay inside the physical-cap arithmetic1-domain sections JSON length 1053,低于断言下限 1200(本命计划改成四标题后 thinking plan 变小了,是预期方向,但断言没跟上)。
  • session-detail JSON at the old ~19-round cap versus the new ~50-round cap19 full rounds already exceeded the old combined quota

要求:按新的 thinking plan 形状重算这两条断言的上下界,写三栏说明;顺带确认 docs 里凡是引用「19 轮 / 50 轮」容量口径的地方是否需要同步。

6. 硬红线(照抄上游任务书,未变)

  1. tsc --noEmit 0 错、npm run lint 0 error、npm test 不得比基线 742ffbc6 的 31 条失败更多(目标是回到 31,即 12 条全绿)。
  2. 测试总数不得低于 3484。
  3. 改任何既有断言写「原值 / 新值 / 原因」三栏;不得用弱化断言换绿。
  4. 不得再引入「先混流再过滤」;新增正则必须能回答「是门还是刀」。
  5. provider reasoning 不进任何对外事件、数据库可见字段、完整原文日志(consultation-budget.ts:55 的 80 字截断日志维持现状即可)。

7. 让步顺序

  1. 先修 BUG-945(P0,用户可感知的空回答路径),再修 946。
  2. BUG-947、948 可与 945 并行(文件不重叠:947 只碰 rectification-agentic/v9/stream-mapping.ts + think-step-gate.ts948 只碰 timing-output-guard.ts / consultation-birth-time-mode.ts / route.ts 的 Pass 4 接线)。
  3. BUG-949 最后做,它依赖前面几条最终的 thinking plan 形状。
  4. 实在做不完,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.mdBUG-945~949+ CHANGELOG.md(若有用户可感知变化),与代码同一批推 staging