Files
Jyotisha/TASK-rectification-provisional-adopt-20260901.md
T
Jesse_Chen 422fc65b22
Independent Staging Quality Gate / validate (push) Successful in 15m33s
Independent Staging Quality Gate / publish (push) Has been cancelled
docs(rectification): add provisional-adopt task brief
采用门对齐引擎语义:provisional 采用成为一等成功出口。
记录三权威冲突根因、经授权推翻的旧红线、以及本地 1993
分钟级收敛系答案泄漏(不构成 web 端目标)的核查结论。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu
2026-09-01 08:01:25 +00:00

15 KiB
Raw Blame History

任务书 · 采用门对齐引擎语义: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 层跑偏):

  • 引擎 receiptrectification-candidate-policy-v2):acceptance_allowed 四门(候选存在 / ≥3 可评分事件 / ≥2 领域 / 日期质量)+ propose_allowed4 事件/3 领域/诊断稳定 事件吻合率 ≥80%)。它老实承认相邻分钟不可分(本 case indistinguishable_width_minutes: 29),所以它的交付物定义就是"代表时间 + 区间"。
  • V10 Skill 文本(10.0.14references/candidate-comparison.md):"唯一领先和宽度≤5 只挡确认门,不挡出示代表性时间卡。"——skill 文本无需改动,本轮不 bump skill 版本。
  • 上游方法论(yinduzhanxing interview_playbook.md):最终交付措辞就是"当前最优结果是候选时间段…临时代表时间仅用于下一轮验证与比较"。

不放宽的东西(新红线,见 §3):唯一分钟确认门、表达边界、证据下限、引擎 ceiling 交集。


1. 事故实证

真实 case f83d9b42-0ac7-411b-9e43-95059b2ede43Skill 10.0.14,快照已存档(向任务发起人索取原始 JSON)。关键形状:

  • 5 条 confirmed 证据(education×1 被引擎划为 holdout、relationship×2、career×2),3 个领域;5 道区分题已答完;候选从 04:45–05:15(30 分钟)收敛到 05:0005:077 分钟),熵 2.00→0.86。流程本身工作正常且收敛良好。
  • 引擎 receiptacceptance_allowed: trueselection_allowed: truepropose_allowed: trueevent_fit_rate.band: "high"80%)、margin_percent: 19.94
  • TS overlay(用户实际看到的):can_adopt: falseselection_allowed: falsepropose_allowed: falsechoice_card: nullinterview.type: "offer_provisional_range"
  • 结果:用户答完约 10 道题、花掉点数,得到一句"当前可信区间是 05:0005:07"的文字,没有候选卡、什么都存不下来,然后被继续追问"有没有记得住时间的收入变化"exhaustion 轮换:finance→occupation→health_pressure→relocation→career→relationship→通用兜底),无限循环。

2. 根因(已静态溯源逐条核实)

根因 1 · deliveryCapability 的两个条件数学上不可达

frontend/src/lib/rectification-agentic/core/rectification-decision.tsdeliveryCapability(约 :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。逐条核对事故 case inference_state.probesexpected_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 要求。overlaydecision-from-dossier.tsoverlayPublicDecision)取交集,于是引擎的"可以"永远被 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_alloweddiagnostic_quality.passed 不联动,是已记录的引擎侧特性(见 BUG_HISTORY / 上轮任务书),本轮不动引擎。
  • 上一轮的非终态出口闸(finalizeSuccessfulTurnExit)工作正常,本 case current_question 非空。保留。

3. 硬红线(本轮新版)

  1. 唯一分钟确认门一个字不放宽。 canConfirmExactMinute 必须继续要求:分离充分 + holdout passed + confirmationAllowed + 引擎 confirmation_allowed(引擎恒 falsefail-closed)。改完后任何路径下 can_confirm_exact_minute 仍不得为 true。
  2. 表达边界不放宽。 "不可分区间 / 代表性候选 / 不是已确认的唯一出生分钟"等措辞、REPRESENTATIVE_MINUTE_DISCLAIMERRECTIFICATION_TERMINATION_COPY 保持;采用后写入 acceptedactive_birth_time),不得confirmed
  3. 证据下限不放宽。 keep_collecting< MIN_STANDALONE_DATED_EVENTS=3 或 < MIN_STANDALONE_DATED_DOMAINS=2)时仍不得采用。四个阈值常量(MIN_SEPARATION_LEAD=8、3/2、3/2)数值不动——本轮只改它们参与哪道门,不改它们的值。
  4. 引擎 ceiling 仍是硬上限。 overlay 对引擎结果只能收紧,不能放宽:receipt 不自洽或 acceptance_allowed=false ⇒ 采用仍关死(engineCapabilityCeilingFromReceipt 的 fail-closed 语义不动)。
  5. coverage 门保留。 coverageBlocks(含 occupation)仍挡采用——它经 exhaustion 轮换可满足,且与 skill 文本一致。
  6. 不 bump skill 版本,不改 skills/** skill 文本已与本轮目标语义一致。
  7. 不修引擎(Python 侧)。 本轮只动 TS 决策层与必要的投影/UI。
  8. 推 staging 前 cd frontend && ./node_modules/.bin/tsc --noEmit 必须通过。不要用 npx tsc(空 worktree 会装到假包 tsc@2.0.4)。
  9. 不改 .gitea/workflows/**;不在有未提交改动的工作树上切分支;不自行把 staging 提升到 main。
  10. 作者无 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)加入:

  1. 事故形状回归(核心):按 f83d9b42 的真实形状构造 dossier——5 条 confirmed 证据(education/relationship×2/career×2education 为 holdout usage)、inference 5 题已答、活跃候选 05:00=24 / 05:07=19 / 04:53=8lead=5 < 8)、引擎 receipt acceptance_allowed/selection_allowed/propose_allowed 全 true、confirmation_allowed=falsediagnostic_quality.passed=false、oos_blind_prompts 剩 family/finance/health_pressure、occupation 未覆盖、family 已拒答。断言:
    • a) occupation 覆盖(confirmed 或拒答关闭)后:can_adopt === trueselection_allowed === truecredible_range["05:00","05:07"]representative_time === "05:00"
    • b) 任何情况下 can_confirm_exact_minute === false
    • c) occupation 仍未覆盖时:can_adopt === falsecurrent_question 语义上存在下一步(沿用上一轮不变量)。
  2. 证据下限保留:同形状但只有 2 条事件或 1 个领域 ⇒ can_adopt === false 且 nextAction 为收集类。
  3. 引擎上限保留:同形状但 receipt acceptance_allowed=false(或字段不自洽触发 fail-closed ceiling)⇒ can_adopt === false
  4. 确认门冻结:构造分离充分 + holdout passed + 引擎 confirmation_allowed=falsecan_confirm_exact_minute === false;引擎四字段自洽且 confirmation true 的合成形状下才允许 true(现网引擎恒 false)。

跑一遍,确认 1a 是红的、其余按当前行为核对。1a 全绿说明测试没写对,重写。

任务 AP0)· deliveryCapability 重构

core/rectification-decision.ts,按符号名定位 deliveryCapability

  • locallySelectable(供 canAdopt / selectionAllowed / proposeAllowed)改为:

    ranked.length > 0
    && !coverageBlocks
    && stopClass?.kind !== "keep_collecting"
    

    移除 separation.sufficientholdout === "passed"exhausted 三个条件。理由:分离与 holdout 移交确认门;exhaustedtied_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 所有出口,无需逐出口改;确认无出口自行覆写这四个字段。

  • isNonConvergingRangeOffercanOfferRange && !canAdopt && ...)语义不变:capability 放行后它自然只在 coverage 未满足或证据不足时为真,exhaustion 收集兜底照旧工作。确认 answer-choice.tspersistNextInterviewIfIdle / persistExhaustionCollect 在新语义下行为正确(occupation 覆盖前仍会追问;覆盖后进入可采用交付,不再轮询)。

任务 BP0)· 交付点必须真的出牌

capability 放行只是数据层。逐条确认交付链路,缺哪补哪:

  1. 投影decideFromDossieroverlayPublicDecision → GET case 快照中 can_adopt/selection_allowed 为 true 时,latest_result.candidatesinterview 投影能驱动前端候选卡(rectification-agentic-chat.tsxRectificationCandidateCards / birth-time-candidate-result)。用户不应需要再发一条消息才看到卡——GET 刷新即可见。
  2. next_user_actiondecideConversationalSession / buildNextUserActionmethod-followup.ts)在 offer/complete 且 canAdopt=true 时应产出 adopt_representative(或等效采用引导),且该轮零追问(skill:不得同一回复既要求补证据又提供采用)。
  3. 采用 RPCcases/[caseId]/candidates/accept 链路上若存在 selection_blocked 类服务端校验,确认其读取的是新语义下的投影(采用不被旧字段挡住);采用后进入既有 verify_adopted_time 核前事流程,不改其逻辑。
  4. 叙述offer 轮正文按 skill §9 出验证报告(候选窗、代表分钟、相对支持、八法理由),并保留"不可分区间/代表性候选"边界句。nonConvergingRangeNarration 在可采用时不应再是唯一出口。

任务 CP1)· 既有断言清点与治理

  • 上一轮的下列不变量按本任务书 §0 授权修改,逐条在 PR 里写"原断言 → 新断言 → 为什么":分离不足不采用、holdout unavailable/not passed 不放行采用、offer_provisional_rangecan_adopt=false、(如存在)tied 不采用。
  • 下列不变量必须保持全绿:证据不足不放行、引擎 ceiling 交集 fail-closed、确认门 fail-closed、非终态出口闸(BUG-456 组)、read_only 无副作用。
  • 修改断言配额:仅限上述授权组;授权组之外不得改既有断言,遇到冲突停下汇报。
  • docs/BUG_HISTORY.md 追加一条:定性为"策略死结"(不是回归),记录三权威冲突、被推翻的红线出处、授权来源与日期。

验收标准

  1. cd frontend && ./node_modules/.bin/tsc --noEmit exit 0。
  2. npx tsx --test tests/rectification-*.test.ts fail=0skill-registry.test.ts 16/16。测试总数允许变化(授权断言组改动 + 新增不变量),在 PR 里给出新基线数并解释增减。
  3. 任务 0 的四组不变量全绿。
  4. 事故形状前后对照贴进 PR:改动前 can_adopt=false + finance 追问循环;改动后 occupation 覆盖 ⇒ can_adopt=true + 候选卡可见 + can_confirm_exact_minute=false
  5. 说明"新增决策分支不会绕过 capability 单点"的机制(capability 只在 deliveryCapability 一处计算、经展开进入全部出口,不得在出口处覆写)。
  6. 未在真实环境验证的部分如实注明。