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
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
# 任务书 · 采用门对齐引擎语义: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%)。它老实承认相邻分钟不可分(本 case `indistinguishable_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,按符号名定位):
|
||||
|
||||
```ts
|
||||
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.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`)工作正常,本 case `current_question` 非空。保留。
|
||||
|
||||
---
|
||||
|
||||
## 3. 硬红线(本轮新版)
|
||||
|
||||
1. **唯一分钟确认门一个字不放宽。** `canConfirmExactMinute` 必须继续要求:分离充分 + holdout passed + `confirmationAllowed` + 引擎 `confirmation_allowed`(引擎恒 false,fail-closed)。改完后任何路径下 `can_confirm_exact_minute` 仍不得为 true。
|
||||
2. **表达边界不放宽。** "不可分区间 / 代表性候选 / 不是已确认的唯一出生分钟"等措辞、`REPRESENTATIVE_MINUTE_DISCLAIMER`、`RECTIFICATION_TERMINATION_COPY` 保持;采用后写入 `accepted`(`active_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. 开工前置
|
||||
|
||||
```bash
|
||||
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×2,education 为 holdout usage)、inference 5 题已答、活跃候选 05:00=24 / 05:07=19 / 04:53=8(lead=5 < 8)、引擎 receipt `acceptance_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` 语义上存在下一步(沿用上一轮不变量)。
|
||||
2. **证据下限保留**:同形状但只有 2 条事件或 1 个领域 ⇒ `can_adopt === false` 且 nextAction 为收集类。
|
||||
3. **引擎上限保留**:同形状但 receipt `acceptance_allowed=false`(或字段不自洽触发 fail-closed ceiling)⇒ `can_adopt === false`。
|
||||
4. **确认门冻结**:构造分离充分 + 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 放行只是数据层。逐条确认交付链路,缺哪补哪:
|
||||
|
||||
1. **投影**:`decideFromDossier` → `overlayPublicDecision` → GET case 快照中 `can_adopt/selection_allowed` 为 true 时,`latest_result.candidates` 与 `interview` 投影能驱动前端候选卡(`rectification-agentic-chat.tsx` 的 `RectificationCandidateCards` / `birth-time-candidate-result`)。**用户不应需要再发一条消息才看到卡**——GET 刷新即可见。
|
||||
2. **next_user_action**:`decideConversationalSession` / `buildNextUserAction`(`method-followup.ts`)在 offer/complete 且 `canAdopt=true` 时应产出 `adopt_representative`(或等效采用引导),且该轮零追问(skill:不得同一回复既要求补证据又提供采用)。
|
||||
3. **采用 RPC**:`cases/[caseId]/candidates/accept` 链路上若存在 `selection_blocked` 类服务端校验,确认其读取的是新语义下的投影(采用不被旧字段挡住);采用后进入既有 `verify_adopted_time` 核前事流程,不改其逻辑。
|
||||
4. **叙述**: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` 追加一条:定性为"策略死结"(不是回归),记录三权威冲突、被推翻的红线出处、授权来源与日期。
|
||||
|
||||
## 验收标准
|
||||
|
||||
1. `cd frontend && ./node_modules/.bin/tsc --noEmit` exit 0。
|
||||
2. `npx tsx --test tests/rectification-*.test.ts` fail=0;`skill-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. 未在真实环境验证的部分如实注明。
|
||||
Reference in New Issue
Block a user