采用门对齐引擎语义:provisional 采用成为一等成功出口。 记录三权威冲突根因、经授权推翻的旧红线、以及本地 1993 分钟级收敛系答案泄漏(不构成 web 端目标)的核查结论。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu
15 KiB
任务书 · 采用门对齐引擎语义:provisional 采用成为一等成功出口(2026-09-01)
基线:origin/staging @ 75fc456d。
0. 决策记录(必读,本轮推翻旧红线是经产品拍板的授权变更)
上一轮任务书(TASK-rectification-nonterminal-exit-20260901.md)写有红线:"不得放宽 deliveryCapability"、"任何让事故 case 变成 canAdopt: true 的改动都是错的"。该红线已由产品负责人于 2026-09-01 显式推翻,理由见下方根因:现行 deliveryCapability 把"可采用"绑在了数学上不可达的条件上,导致没有任何真实用户能走完一次成功流程。本轮的目标:
当引擎按方法论判定可以出牌时,用户必须真的能采用"代表分钟 + 可信区间"(provisional)。分钟级分离与 holdout 只保留给"唯一分钟确认"这道门。
依据(三个权威本来就一致,只有 TS 层跑偏):
- 引擎 receipt(
rectification-candidate-policy-v2):acceptance_allowed四门(候选存在 / ≥3 可评分事件 / ≥2 领域 / 日期质量)+propose_allowed(4 事件/3 领域/诊断稳定 或 事件吻合率 ≥80%)。它老实承认相邻分钟不可分(本 caseindistinguishable_width_minutes: 29),所以它的交付物定义就是"代表时间 + 区间"。 - V10 Skill 文本(10.0.14,
references/candidate-comparison.md):"唯一领先和宽度≤5 只挡确认门,不挡出示代表性时间卡。"——skill 文本无需改动,本轮不 bump skill 版本。 - 上游方法论(yinduzhanxing
interview_playbook.md):最终交付措辞就是"当前最优结果是候选时间段…临时代表时间仅用于下一轮验证与比较"。
不放宽的东西(新红线,见 §3):唯一分钟确认门、表达边界、证据下限、引擎 ceiling 交集。
1. 事故实证
真实 case f83d9b42-0ac7-411b-9e43-95059b2ede43,Skill 10.0.14,快照已存档(向任务发起人索取原始 JSON)。关键形状:
- 5 条 confirmed 证据(education×1 被引擎划为 holdout、relationship×2、career×2),3 个领域;5 道区分题已答完;候选从 04:45–05:15(30 分钟)收敛到 05:00–05:07(7 分钟),熵 2.00→0.86。流程本身工作正常且收敛良好。
- 引擎 receipt:
acceptance_allowed: true、selection_allowed: true、propose_allowed: true、event_fit_rate.band: "high"(80%)、margin_percent: 19.94。 - TS overlay(用户实际看到的):
can_adopt: false、selection_allowed: false、propose_allowed: false、choice_card: null、interview.type: "offer_provisional_range"。 - 结果:用户答完约 10 道题、花掉点数,得到一句"当前可信区间是 05:00–05:07"的文字,没有候选卡、什么都存不下来,然后被继续追问"有没有记得住时间的收入变化"(exhaustion 轮换:finance→occupation→health_pressure→relocation→career→relationship→通用兜底),无限循环。
2. 根因(已静态溯源逐条核实)
根因 1 · deliveryCapability 的两个条件数学上不可达
frontend/src/lib/rectification-agentic/core/rectification-decision.ts 的 deliveryCapability(约 :196,按符号名定位):
const locallySelectable = separation.ranked.length > 0
&& !coverageBlocks
&& stopClass?.kind !== "keep_collecting"
&& stopClass?.kind !== "exhausted"
&& separation.sufficient // ← 不可达之一
&& holdout === "passed"; // ← 不可达之二
separation.sufficient不可达:需要 top-2 领先 ≥MIN_SEPARATION_LEAD=8,计分制每题 ±2。逐条核对事故 caseinference_state.probes的expected_outcomes:所有剩余可问的题(dasha 边界题、nakshatra 边界题)都把 05:00 与 05:07 分在同一组,答什么都同涨同跌。唯一能分开这两个分钟的是 d24/d12 等 varga 对比题,但全部被yearless_ungrounded_contrast政策丢进dropped_probes。lead 永远停在 5。holdout === "passed"事实上不可达:ask_holdout_validation分支在decideRectification里位于 coverage 检查之后、且要求 probe 耗尽;即使问到并通过,第一条仍然封死canAdopt。- 这不是单 case 现象:任何"约 5 条真实事件 + 31 分钟候选网格"的普通用户都落在同一形状上。成功率结构性为零。
根因 2 · 引擎与 TS 权威冲突时,TS 永远向"否"裁决
引擎与 skill 文本都认为本轮可出牌;TS deliveryCapability(来自 9f011194/4b133177 两轮收紧)单方面追加了分离+holdout 要求。overlay(decision-from-dossier.ts 的 overlayPublicDecision)取交集,于是引擎的"可以"永远被 TS 的"不行"覆盖。
根因 3 · "本地 Agent 能收敛到 1-2 分钟"不构成 web 端目标(答案泄漏,勿对标)
有一组本地对话记录(yinduzhanxing 根目录 1993生时校正对话-*.txt)显示本地 Agent 调用同一 skill 能把 1993 案例收敛到 14:49。那不是校时能力,是答案泄漏:本地仓里存有该用户的既有结论(qizheng_1993_native_current_report_2026_08_26.md 记录 14:49:00、候选段表钉死 14:48-14:50、classical_zr_known_user_case_calibration_1993 回归包),Agent 在对话里两次自认"14:49 主要被仓里的已知校准档锁住,不是单靠回答算出来的",随后更把 target_minute=14:49 作为回归提示直接写进了脚本(该污染只在上游 codex/add-birth-time-rectification-skill 分支 commit 3bb90620,已核实未进入本产品仓与 vendored skill 快照,grep pl9_1993|target_minute|regression_only 为零命中)。web 端面对无答案库的陌生用户,7 分钟不可分区间 + 80% 拟合才是 5 条事件的真实信息量上限。任何人(包括测试)不得以"收敛到唯一分钟"作为本轮验收口径,也不得把该上游分支的回归提示代码引入产品。
附带确认(本轮不修)
- coverage 死锁不存在:
exhaustionSpokenCollectFollowup的轮换在 family/education/finance 之后会问 occupation,且拒答(occupationCollectFocusClosed)也算覆盖。coverage 门保留。 - 引擎 receipt 的
acceptance_allowed与diagnostic_quality.passed不联动,是已记录的引擎侧特性(见 BUG_HISTORY / 上轮任务书),本轮不动引擎。 - 上一轮的非终态出口闸(
finalizeSuccessfulTurnExit)工作正常,本 casecurrent_question非空。保留。
3. 硬红线(本轮新版)
- 唯一分钟确认门一个字不放宽。
canConfirmExactMinute必须继续要求:分离充分 + holdout passed +confirmationAllowed+ 引擎confirmation_allowed(引擎恒 false,fail-closed)。改完后任何路径下can_confirm_exact_minute仍不得为 true。 - 表达边界不放宽。 "不可分区间 / 代表性候选 / 不是已确认的唯一出生分钟"等措辞、
REPRESENTATIVE_MINUTE_DISCLAIMER、RECTIFICATION_TERMINATION_COPY保持;采用后写入accepted(active_birth_time),不得写confirmed。 - 证据下限不放宽。
keep_collecting(<MIN_STANDALONE_DATED_EVENTS=3或 <MIN_STANDALONE_DATED_DOMAINS=2)时仍不得采用。四个阈值常量(MIN_SEPARATION_LEAD=8、3/2、3/2)数值不动——本轮只改它们参与哪道门,不改它们的值。 - 引擎 ceiling 仍是硬上限。 overlay 对引擎结果只能收紧,不能放宽:receipt 不自洽或
acceptance_allowed=false⇒ 采用仍关死(engineCapabilityCeilingFromReceipt的 fail-closed 语义不动)。 - coverage 门保留。
coverageBlocks(含 occupation)仍挡采用——它经 exhaustion 轮换可满足,且与 skill 文本一致。 - 不 bump skill 版本,不改
skills/**。 skill 文本已与本轮目标语义一致。 - 不修引擎(Python 侧)。 本轮只动 TS 决策层与必要的投影/UI。
- 推 staging 前
cd frontend && ./node_modules/.bin/tsc --noEmit必须通过。不要用npx tsc(空 worktree 会装到假包tsc@2.0.4)。 - 不改
.gitea/workflows/**;不在有未提交改动的工作树上切分支;不自行把 staging 提升到 main。 - 作者无 staging 环境凭据,根因来自静态溯源 + 事故快照。你若同样无凭据,不得声称已在真实环境验证。
让步顺序:用户能拿到诚实且可采用的结果 > 确认门与表达边界不放宽 > 功能与测试不回归 > 代码整洁。
4. 开工前置
git fetch origin --prune
git worktree add -b codex/rectification-provisional-adopt-20260901 \
../.worktrees/rectification-provisional-adopt-20260901 origin/staging
读 docs/research/pre_work_error_ledger.md,跑 scripts/pre_work_check.py,读 frontend/AGENTS.md。在 docs/BUG_HISTORY.md 检索 BUG-456 / BUG-459 及上两轮记录。行号只是线索,按符号名定位。
任务 0(门控)· 先写不变量,先让它红
在 frontend/tests/rectification-decision-authority.test.ts(或新文件 rectification-provisional-adopt.test.ts)加入:
- 事故形状回归(核心):按
f83d9b42的真实形状构造 dossier——5 条 confirmed 证据(education/relationship×2/career×2,education 为 holdout usage)、inference 5 题已答、活跃候选 05:00=24 / 05:07=19 / 04:53=8(lead=5 < 8)、引擎 receiptacceptance_allowed/selection_allowed/propose_allowed全 true、confirmation_allowed=false、diagnostic_quality.passed=false、oos_blind_prompts 剩 family/finance/health_pressure、occupation 未覆盖、family 已拒答。断言:- a) occupation 覆盖(confirmed 或拒答关闭)后:
can_adopt === true且selection_allowed === true,credible_range为["05:00","05:07"],representative_time === "05:00"; - b) 任何情况下
can_confirm_exact_minute === false; - c) occupation 仍未覆盖时:
can_adopt === false且current_question语义上存在下一步(沿用上一轮不变量)。
- a) occupation 覆盖(confirmed 或拒答关闭)后:
- 证据下限保留:同形状但只有 2 条事件或 1 个领域 ⇒
can_adopt === false且 nextAction 为收集类。 - 引擎上限保留:同形状但 receipt
acceptance_allowed=false(或字段不自洽触发 fail-closed ceiling)⇒can_adopt === false。 - 确认门冻结:构造分离充分 + holdout passed + 引擎
confirmation_allowed=false⇒can_confirm_exact_minute === false;引擎四字段自洽且 confirmation true 的合成形状下才允许 true(现网引擎恒 false)。
跑一遍,确认 1a 是红的、其余按当前行为核对。1a 全绿说明测试没写对,重写。
任务 A(P0)· deliveryCapability 重构
core/rectification-decision.ts,按符号名定位 deliveryCapability:
-
locallySelectable(供canAdopt/selectionAllowed/proposeAllowed)改为:ranked.length > 0 && !coverageBlocks && stopClass?.kind !== "keep_collecting"即移除
separation.sufficient、holdout === "passed"、exhausted三个条件。理由:分离与 holdout 移交确认门;exhausted(tied_first / user_uncertainty_too_high)按引擎与 skill 语义只影响措辞与确认门——tie 时引擎照常给出代表候选,unsure 答案本来就不计分。 -
canConfirmExactMinute显式补回严格条件:locallySelectable && separation.sufficient && holdout === "passed" && confirmationAllowed && engineCeiling.confirmationAllowed(红线 1:净效果必须与现状等价或更严。)
-
capability已通过...capability展开进入collect/discriminate/holdoutValidation/offerRangeWithoutAdopt/completeWithRange/finish所有出口,无需逐出口改;确认无出口自行覆写这四个字段。 -
isNonConvergingRangeOffer(canOfferRange && !canAdopt && ...)语义不变:capability 放行后它自然只在 coverage 未满足或证据不足时为真,exhaustion 收集兜底照旧工作。确认answer-choice.ts的persistNextInterviewIfIdle/persistExhaustionCollect在新语义下行为正确(occupation 覆盖前仍会追问;覆盖后进入可采用交付,不再轮询)。
任务 B(P0)· 交付点必须真的出牌
capability 放行只是数据层。逐条确认交付链路,缺哪补哪:
- 投影:
decideFromDossier→overlayPublicDecision→ GET case 快照中can_adopt/selection_allowed为 true 时,latest_result.candidates与interview投影能驱动前端候选卡(rectification-agentic-chat.tsx的RectificationCandidateCards/birth-time-candidate-result)。用户不应需要再发一条消息才看到卡——GET 刷新即可见。 - next_user_action:
decideConversationalSession/buildNextUserAction(method-followup.ts)在 offer/complete 且canAdopt=true时应产出adopt_representative(或等效采用引导),且该轮零追问(skill:不得同一回复既要求补证据又提供采用)。 - 采用 RPC:
cases/[caseId]/candidates/accept链路上若存在selection_blocked类服务端校验,确认其读取的是新语义下的投影(采用不被旧字段挡住);采用后进入既有verify_adopted_time核前事流程,不改其逻辑。 - 叙述:offer 轮正文按 skill §9 出验证报告(候选窗、代表分钟、相对支持、八法理由),并保留"不可分区间/代表性候选"边界句。
nonConvergingRangeNarration在可采用时不应再是唯一出口。
任务 C(P1)· 既有断言清点与治理
- 上一轮的下列不变量按本任务书 §0 授权修改,逐条在 PR 里写"原断言 → 新断言 → 为什么":分离不足不采用、holdout unavailable/not passed 不放行采用、
offer_provisional_range恒can_adopt=false、(如存在)tied 不采用。 - 下列不变量必须保持全绿:证据不足不放行、引擎 ceiling 交集 fail-closed、确认门 fail-closed、非终态出口闸(BUG-456 组)、
read_only无副作用。 - 修改断言配额:仅限上述授权组;授权组之外不得改既有断言,遇到冲突停下汇报。
docs/BUG_HISTORY.md追加一条:定性为"策略死结"(不是回归),记录三权威冲突、被推翻的红线出处、授权来源与日期。
验收标准
cd frontend && ./node_modules/.bin/tsc --noEmitexit 0。npx tsx --test tests/rectification-*.test.tsfail=0;skill-registry.test.ts16/16。测试总数允许变化(授权断言组改动 + 新增不变量),在 PR 里给出新基线数并解释增减。- 任务 0 的四组不变量全绿。
- 事故形状前后对照贴进 PR:改动前
can_adopt=false+ finance 追问循环;改动后 occupation 覆盖 ⇒can_adopt=true+ 候选卡可见 +can_confirm_exact_minute=false。 - 说明"新增决策分支不会绕过 capability 单点"的机制(capability 只在
deliveryCapability一处计算、经展开进入全部出口,不得在出口处覆写)。 - 未在真实环境验证的部分如实注明。