8db71aaf81
- README.md is now the product/repo front door (architecture, repo map, local dev, test tiers, delivery flow, doc map). Engine positioning, VedAstro/Codex setup and the oracle/benchmark command reference move verbatim to docs/engine/README.md, docs/engine/vedastro-gateway.md and docs/benchmark/README.md. Capability badges realigned with the registry (91/78/8/0); tests/test_readme_badges.py was red on staging. - AGENTS.md: Part A (environment truth, delivery, worktrees, record placement, bug workflow, growth freeze, frontend red lines, privacy, pre-work check, test tiers) and Part B (reading-rigor constraints). GitHub issue-tracker/triage boilerplate removed: GitHub is a read-only mirror. All strings locked by tests/ are preserved. - CLAUDE.md added: roles, three working modes, task-brief sections, acceptance criteria, session discipline; imports AGENTS.md. - 50 tracked TASK-*/PROGRESS-* files and 3 never-committed briefs move to docs/tasks/ with an index; REPO_LAYOUT.md merged into README. Docs-only change (no gated path touched). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
505 lines
30 KiB
Markdown
505 lines
30 KiB
Markdown
# 任务书 · 生时校正收敛重构(2026-08-30,v2)
|
||
|
||
基线:`origin/staging` @ `7db2dd2d`。
|
||
|
||
**下面所有行号只是线索,请按选择器/函数名定位**,后续提交可能让行号偏移。标注为「上游」的行号来自对 `/workspace/yinduzhanxing` 的一次调研,动手前请自行复核。
|
||
|
||
> **v2 说明**:v1 的判断是「线上打分器可能不如本地方法学,需要三方盲测决定是否换引擎」。对上游仓库 `/workspace/yinduzhanxing` 做完比对后,这个判断被推翻了。换引擎的路线已排除,本版的落地点比 v1 小一个数量级。v1 的任务 0(三方盲测)作废。
|
||
|
||
## 为什么要做
|
||
|
||
### 事实 1 · 方法学没有丢,也没有落后
|
||
|
||
`/workspace/Jyotisha` 与上游 `/workspace/yinduzhanxing` 同源(共同初始提交 `a5bbba28`,2026-04-20),分叉于 `825a9ea5`(2026-07-17)。分叉后上游 808 个提交、本仓 1,275 个提交,**互相同步 0 次**。
|
||
|
||
但生时校正方法学的三份文件 `cmp` 逐字节相同:
|
||
|
||
```
|
||
IDENTICAL birth-time-rectification-advanced.md
|
||
IDENTICAL birth-time-rectification-decision-tree.md
|
||
IDENTICAL birth-time-rectification-cases.md
|
||
```
|
||
|
||
(`yinduzhanxing/references/` ↔ `Jyotisha/skills/jyotish-vedic-astrology/versions/6.9.14/references/`)
|
||
|
||
上游六周里在校正上新增的是治理、契约与复现文档,不是方法学本身。**「本地版本更新」不成立。**
|
||
|
||
### 事实 2 · 三套打分链路是同一算法家族
|
||
|
||
- `Jyotisha/scripts/rectification/scoring_service.py:11` 直接 `from scripts.active_rectification_event_engine import ...` —— 线上 v5 调用的就是上游那个事件引擎。
|
||
- `Jyotisha/scripts/minute_rectification_fact_ranker_v4.py:16` import 同一个 `DOMAIN_CONFIG`。
|
||
- 两边共享同一套权重:Vimshottari MD/AD/PD = `2.0 / 1.5 / 0.75`,Narayana MD/AD = `2.0 / 1.0`。
|
||
|
||
因此 `references/rectification_sealed_holdout.v1.json` 那组数字(`top_1_rate 0.15`、`mean_absolute_minute_error 6.95`、`confirmation_coverage_rate 0.0`)虽然测的是 v4,**对线上 v5 是弱但真实的先验,不能当作无关数据**。上游也没有更好的打分器可换。
|
||
|
||
### 事实 3 · 真正丢掉的是「停下来」的机制
|
||
|
||
上游三条链路每一条都天然终止:
|
||
|
||
| 上游链路 | 终止方式 |
|
||
| --- | --- |
|
||
| `scripts/active_rectification_questions.py` | `QUESTION_TEMPLATES` 是**写死的 8 题**分 3 轮;问完 `workflow_status` 转 `dated_event_review_required`(上游 :537) |
|
||
| `scripts/active_rectification_events.py` | `adjudicate_candidate_rows()` **一次性批量裁决**(上游 :212),算完直接给 low/medium/high |
|
||
| `jyotish-app/rectification-engine.js` | 浏览器内一次算完给置信度标签,无追问 |
|
||
|
||
且上游**从不承诺分钟**:`can_apply` 默认 `False`、`truth_status: not_birth_time_truth` 写死在代码里。
|
||
|
||
Jyotisha 把这套替换成了服务端逐轮动态生成 discriminator probe 的**无界循环**。
|
||
|
||
### 事实 4 · 无限访谈的机械解释
|
||
|
||
生产判定 `frontend/src/lib/rectification-agentic/core/rectification-decision.ts:144-149`:
|
||
|
||
```ts
|
||
if (!separation.sufficient) {
|
||
if (probe && !userStopped) return discriminate(...); // 只要有探针就继续问
|
||
return completeWithRange(..., "offer");
|
||
}
|
||
```
|
||
|
||
**这条路径里没有任何轮次计数器。** 同时:
|
||
|
||
- `confirmation_coverage_rate: 0.0` —— sealed holdout 的 20 例里**零例**达到确认门,即门几乎不会开。
|
||
- probe 的 `semantic_key` 按 `domain.year.month` 构造,随候选集每轮重新生成,实际不会耗尽。
|
||
|
||
**门永远不开 + 探针永不耗尽 = 无限访谈。** 这是全部 stall 类 bug 的根因。
|
||
|
||
### 事实 5 · 8 轮上限算了,但没人拿它当终止条件
|
||
|
||
不要误以为它是死代码。生产链路确实在跑它:
|
||
|
||
```
|
||
mastra/rectification-v9-tools.ts:945 → buildCaseInferenceState
|
||
→ v9/inference-adapter.ts:248 → buildInferenceState
|
||
→ core/build-state.ts:162 → evaluateConvergence
|
||
→ core/convergence-evaluator.ts:54 maxRounds = state.max_rounds ?? DEFAULT_MAX_DISCRIMINATION_ROUNDS
|
||
→ core/types.ts:10 DEFAULT_MAX_DISCRIMINATION_ROUNDS = 8
|
||
```
|
||
|
||
`max_rounds` 没有被传入,取默认 8,算出的 `result_status` 也写进了 state。**但 `rectification-decision.ts` 的判定不读它。**
|
||
|
||
所以修法**不是**「启用死代码」(去改 `convergence-evaluator.ts` 改了也没用),而是**把轮次预算接进 `rectification-decision.ts` 的判定输入**。
|
||
|
||
而且终止分支已经存在:`rectification-decision.ts:158` 就有 `completeWithRange(separation, holdout, range, "exhausted")`,`:336` 的 `kind` 枚举也已支持 `"exhausted"`。**代码路径是现成的,只是没人触发。**
|
||
|
||
另有一套完整但闲置的停机策略:`frontend/src/lib/birth-time-dynamic-stop-policy.ts:43-48`(高置信 / 答满 10 条 safety cap / 平台期 2 轮 / 零信息增益 / 重复分区),属旧链路,V9 不走它。
|
||
|
||
### 事实 6 · 上游同样没有盲测证据
|
||
|
||
`yinduzhanxing/references/real_case_calibration/timing_rectification_*_2026_07_23.json`:
|
||
|
||
```
|
||
blind_replay_allowed = false
|
||
ready_case_count = 0 (minimum_case_count = 3)
|
||
reviewer_2_confirmed_count = 0
|
||
status = blocked_until_human_labels
|
||
production_tuning_allowed = false
|
||
progress.md:1611 — "zero cases ready for blind replay"
|
||
```
|
||
|
||
`birth-time-rectification-cases.md` 里的奥巴马/居里夫人案例,是**拿已知出生时间回验事件**并自评「吻合度 95%」——知道答案的复盘,不是校正测试。
|
||
|
||
**两边都没有盲测数据。产品侧「本地能收敛」的观察,最可能是定义差异**:上游问完固定题数就给候选区间并停下,那个体验就是「收敛」;按代码它给的一定是区间 + low/medium/high 标签,不是确定的分钟。
|
||
|
||
### 结论
|
||
|
||
产品目标定为:**交付收窄后的区间,不交付唯一分钟。** 唯一分钟保留为 sealed holdout 通过后才开启的路径。
|
||
|
||
要做的是**把上游那套「问完就停、停下来时坦白交代」的产品契约接回来**,不是重写收敛逻辑,更不是换打分引擎。
|
||
|
||
---
|
||
|
||
## 硬红线
|
||
|
||
1. **任务 0 是门控,但它只是一次信息收集,不许写代码。** 在拿到上游访谈手册与一份真实会话记录之前,不得改动 `rectification-decision.ts` 的判定。
|
||
2. **诚实性不可让渡。** 不得为了让访谈「看起来能收敛」而放宽 `confirmation-gate.ts` 的任何 blocker,不得降低 `SEALED_MINUTE_HOLDOUT` 门槛,不得在 `confirmation_allowed=false` 时宣称唯一分钟。这套 fail-closed 机制现在正在正确工作。
|
||
3. **禁止答案回流。** 任何以已知答案调出来的规则,不得进入生产打分链路,只能存在于标注 `regression_only` 且被评测明确排除的夹具里。上游已经发生过一次(见任务 0 补充),本仓当前干净,必须保持。
|
||
4. **上游同步必须过污染审查。** 不得同步任何带 `pl9_1993`、`observed_*_case_only`、`target_minute` 标记的打分逻辑,特别是上游 `scripts/narayana_dasha.py:601-613` 的 tie-break。任务 5 的同步机制必须包含这道检查。
|
||
5. **超预算的出口必须是「交付区间」,不是「失败」。** 走 `completeWithRange(..., "exhausted")`,用户拿到候选区间与明确的边界说明。任何把超预算实现成报错、静默停止或空白页的做法都是错的。
|
||
6. **不得用 holdout 调参。** `minute_rectification_holdout_v3` 的 `source_audit_status` 是 `invalidated_after_replay`,只能当开发集看趋势,**不得作为发布指标,不得写回 sealed 字段**。`v4_intake` 的 `production_tuning_allowed` 保持 `false`。
|
||
7. **不要引入第五套打分器。** 不要移植 `jyotish-app/rectification-engine.js` 的 40/35/15/10 权重。它信号更弱(无 Narayana / Arudha / Ashtakavarga / Shadbala)且从未评测。
|
||
8. **状态机留在 Postgres 函数里**,沿用既有模式。不得把状态机搬进应用层。
|
||
9. **不得修改既有测试断言** —— 除非该断言锁的正是本轮要改的缺陷本身;那种情况必须在断言上方注明原值与原因,并在 PROGRESS 单列。
|
||
10. 推 staging 前必须 `./node_modules/.bin/tsc --noEmit` 通过。**不要用 `npx tsc`**,本仓库环境下会装到空包 `tsc@2.0.4`。
|
||
11. **数据库测试必须真跑。** `npm run test:db` 需要 Docker。没有 Docker 就不要推 —— 写进 `BLOCKED.md` 并停下。
|
||
12. 不得改 `.gitea/workflows/**`。不得在有未提交改动的工作树上切分支。不得自行把 staging 提升到 main。
|
||
|
||
让步顺序:**诚实性不回退 > 数据不损坏 > 功能与测试不回归 > 可验证的收敛改进 > 代码整洁 > 成本**。
|
||
|
||
## 开工前置
|
||
|
||
```bash
|
||
git fetch origin --prune
|
||
git worktree add -b codex/rectification-convergence-20260830 \
|
||
../.worktrees/rectification-convergence-20260830 origin/staging
|
||
```
|
||
|
||
读 `pre_work_error_ledger.md`,跑 `scripts/pre_work_check.py`,读 `frontend/AGENTS.md`。改前在 `docs/BUG_HISTORY.md` 检索校正相关记录(30 条以上,停滞类那几条务必读完)。
|
||
|
||
**先读这些再动手:**
|
||
|
||
- `frontend/src/lib/rectification-agentic/core/rectification-decision.ts` —— 生产判定,本轮主战场
|
||
- `frontend/src/lib/rectification-agentic/core/convergence-evaluator.ts` + `core/build-state.ts:162` —— 已在跑但没被消费的轮次判定
|
||
- `frontend/src/lib/birth-time-dynamic-stop-policy.ts:43-48` —— 现成但闲置的停机策略
|
||
- `frontend/src/lib/rectification-agentic/v9/spoken-answer.ts` —— 101 条清洗正则,任务 2 要删
|
||
- `frontend/src/mastra/agentic-rectification.ts:61-68` —— 系统提示,任务 2 要重写第 4 条
|
||
- 上游 `/workspace/yinduzhanxing/scripts/active_rectification_events.py:115-179` —— 任务 3 要移植的 `build_candidate_result_summary()`
|
||
- 上游 `/workspace/yinduzhanxing/jyotish-app/rectification-collaboration.js` —— 四阶段状态机,参考它有多简单
|
||
|
||
---
|
||
|
||
## 任务 0(已完成 2026-08-31)· 上游访谈手册与阈值文档
|
||
|
||
三份文件已获取,归档于 `references/upstream/`(来源:上游维护者本地 skill 包 `~/.workbuddy/skills/jyotish-birth-time-rectification/`,获取日期 2026-08-31):
|
||
|
||
- `references/upstream/interview_playbook.md`
|
||
- `references/upstream/evidence_thresholds.md`
|
||
- `references/upstream/test_birth_time_rectification_skill_contract.py`
|
||
|
||
### 结论:上游的「收敛」= 从不试图确认分钟
|
||
|
||
**1. 停止规则与本仓方向相反。**
|
||
|
||
`interview_playbook.md` 的 Stop Rules 原文:
|
||
|
||
```
|
||
Stop and report the current interval when any is true:
|
||
- fewer than three dated events
|
||
- fewer than two domains
|
||
- tied first-place candidates
|
||
- user uncertainty is too high to map support/conflict reliably
|
||
```
|
||
|
||
即:**证据不足 → 停下来,交付当前区间。**
|
||
|
||
本仓 `frontend/src/lib/birth-time-evidence.ts:188-189` 把 `minConfirmationEvents(4)` / `minConfirmationDomains(3)` 当作**确认门**,达不到 → **继续问**。
|
||
|
||
同一类阈值,上游当「可以开始交付的地板」,本仓当「可以下结论的天花板」。前者三轮内必达,后者按 sealed holdout 的 `confirmation_coverage_rate: 0.0` 是零达成。**这是无限访谈的最终解释。**
|
||
|
||
**2. 上游的标签梯子没有「已确认」这一档。**
|
||
|
||
`evidence_thresholds.md`:`blocked → user_history_verification_required → manual_pattern_consensus → single_adapter_support → multi_adapter_consensus`。顶端自注 "this is still not birth-time truth";`standalone_core` 模式天花板更低,只到 `manual_pattern_consensus`。
|
||
|
||
`test_birth_time_rectification_skill_contract.py` 中**每一个** case(含最高档 `main_repository_enhanced`)都断言:
|
||
|
||
```python
|
||
assert receipt["claim_status"] == "candidate_range_not_birth_time_truth"
|
||
```
|
||
|
||
**3. 提问节奏是硬的三轮。**
|
||
|
||
第 1 轮 3–5 道 A/B/C/D 选择题;第 2 轮只要最强 domain 的带日期事件;第 3 轮只问能区分 top 候选的 1–3 个事件。手册明写 "Never collect a long autobiography before the first route decision."
|
||
|
||
**4. 终止话术是写死的。**
|
||
|
||
> 当前最优结果是候选时间段,而不是已经确认的唯一出生分钟。临时代表时间仅用于下一轮验证与比较。
|
||
|
||
### 补充(2026-08-31)· 两份真实会话记录证明「本地收敛」源于答案泄漏
|
||
|
||
会话记录已归档:`references/upstream/1993-session-repeated-questions.txt`、`references/upstream/1993-session-correction-rerun.txt`。
|
||
|
||
Agent 在记录中两次自陈:
|
||
|
||
> 我明显受到了仓里这份 1993 案例的现成高质量材料影响…不是纯靠你当下问答独立推出来的。
|
||
|
||
> 14:49 这个点,主要还是被仓里的 1993 已知校准档锁住了,不是单靠这些回答「算出来」的唯一真值。
|
||
|
||
答案原本就写在上游 `docs/research/qizheng_1993_native_current_report_2026_08_26.md`(`1993-04-17 14:49:00`)。随后的「修正」是把答案接进打分链路(`pl9_1993_user_case`,`target_minute = 14:49`,提交 `3bb90620`)。进一步核查发现上游打分器里已有按该案例调出的规则:
|
||
|
||
```
|
||
scripts/narayana_dasha.py:601 "selection_levels": ["observed_pl9_1993_gemini_sagittarius_tie_break"]
|
||
scripts/narayana_dasha.py:613 evidence_status = "observed_pl9_1993_case_only"
|
||
```
|
||
|
||
**本仓已核查未受污染**(全仓 grep `pl9_1993` / `target_minute` / `14:49` 无相关命中)。红线 3、4 用于保持这一状态。
|
||
|
||
**但记录同时证明了真实能力**:用户的 8 条生平锚点把窗口从 14:30–15:00(30 分钟)压到 14:48–14:50(3 分钟)。**收窄是真的,选定分钟不是。** 这与 `mean_absolute_minute_error: 6.95` 及上游标签梯子顶端「still not birth-time truth」完全一致,并再次确认产品目标应为交付区间。
|
||
|
||
### 对已完成任务的影响
|
||
|
||
任务 1 实现的 `budgetExhausted()`(轮次 8 / 答题 10 / 平台期 2)方向正确但只覆盖了一半——上游是**证据状态型**停止且三轮即止。差额由**任务 6** 补齐。
|
||
|
||
---
|
||
|
||
## 任务 1(P0)· 把轮次预算接进生产判定
|
||
|
||
### 事实
|
||
|
||
见事实 4、5。`rectification-decision.ts:144-149` 的 `!separation.sufficient` 分支只要有 probe 且用户没喊停就无限 `discriminate`。轮次判定在别处算好了但没人读。
|
||
|
||
### 要做什么
|
||
|
||
1. 给 `rectification-decision.ts` 的判定输入增加**预算三件套**,参数一律取自已有常量,**不要新造数字**:
|
||
- 轮次上限 —— `core/types.ts:10` 的 `DEFAULT_MAX_DISCRIMINATION_ROUNDS = 8`
|
||
- 平台期 —— `references/rectification_policy.v1.json` 的 `maxPlateauRounds`(当前只有 `scripts/rectification_policy.py` 读了,前端没用)
|
||
- 安全帽 —— `birth-time-dynamic-stop-policy.ts:45` 的 `effectiveAnswerCount >= 10`
|
||
2. 在 `!separation.sufficient` 分支的 `discriminate(...)` **之前**插入预算检查。超预算时走**已存在的** `completeWithRange(separation, holdout, range, "exhausted")`(:158)。
|
||
3. **不要去改 `convergence-evaluator.ts`。** 它算得没错,问题是没人消费。要么把 `result_status` 传进判定输入,要么在判定层独立计数——二选一,在 PROGRESS 说明选了哪个及理由。
|
||
4. 轮次计数必须**服务端持久化**,不得靠对话历史推断。沿用既有的 receipt / turn 计数来源。
|
||
|
||
### 属性测试(本任务的核心交付)
|
||
|
||
在 `frontend/tests/` 新增:
|
||
|
||
- **可终止**:从任意状态出发,反复喂 `discriminate` 返回的 probe 的答案,必须在 ≤ 预算轮数内到达某个 `completeWithRange` 分支。**不存在无限 discriminate 循环。**
|
||
- **拒答不卡死**:用户对每一个 probe 都拒答时必须到达终态,不得停在 `discriminate`。
|
||
- **超预算即交付**:超预算时返回的是 `"exhausted"` 且带候选区间,不是错误或空结果。
|
||
- **单调性**:追加证据后区间宽度只能变小或不变,永不变大。
|
||
|
||
这四条一次性锁死未来所有 stall 类 bug。
|
||
|
||
### 验收
|
||
|
||
- 四条属性测试全绿。
|
||
- 停滞类既有测试(`rectification-collect-stall.test.ts`、`rectification-range-offer-deadend.test.ts` 等)全绿。
|
||
- 端到端:一个用户即使一直不给有效证据,也会在预算内拿到区间并结束。
|
||
|
||
---
|
||
|
||
## 任务 2(P0)· 提问权收归服务端,模型降级为一句确认
|
||
|
||
### 事实
|
||
|
||
一条消息目前有**四个作者**:服务端决定问什么(`v9/method-followup.ts`,2,036 行)→ 模型写正文 → 服务端把题干接在正文之后 → UI 再渲染选择卡。
|
||
|
||
于是 `src/mastra/agentic-rectification.ts:66` 要求模型判断自己处在 `choice` / `collect_spoken` / 无持久化问题 三种状态中的哪一种再区别行动。模型做不到,所以有了 `v9/spoken-answer.ts`:**33 条内部标识符正则 + 68 条中文过程话术正则,共 101 条**,事后擦模型漏出的内心戏。每一条正则都是一个曾经发生过的 bug。
|
||
|
||
### 要做什么
|
||
|
||
1. **模型永远不提问。** 一轮里它的唯一职责是针对用户刚说的内容写**一句**确认/承接。系统提示第 4 条整条重写,删掉所有关于题干归属的条件分支。
|
||
2. **问题永远由服务端渲染,永远出现在同一个问题区。** `choice` 与 `collect_spoken` 合并成一个「问题槽」:有选项就可点,没选项就是输入框加提示。数据模型不再区分 kind,UI 不再有两条渲染路径。
|
||
3. **取消「服务端把题干接在模型正文之后」的拼接。** 一条消息 = 模型的一句确认(流式)+ 服务端问题区(结构化渲染),两者物理分离,各自唯一作者。
|
||
4. **删掉 `spoken-answer.ts` 的 101 条清洗正则。** 如果删完发现仍需清洗,说明第 1 步没做干净,回去改第 1 步,**不要把正则加回来**。
|
||
5. 服务端未产出问题时问题区为空 —— 这是可断言、可监控的显式状态,不是静默停滞。加服务端告警。
|
||
|
||
### 验收
|
||
|
||
- 用户可见文本不可能出现内部标识符或过程话术,且**不是靠正则保证**,是靠模型输出面收窄保证。
|
||
- 端到端连续 10 轮,问题始终在问题区,位置一致,不重复、不消失。
|
||
- `spoken-answer.ts` 的两条大正则不再存在于代码库。
|
||
|
||
---
|
||
|
||
## 任务 3(P0)· 恢复「停下来时怎么交代」的契约,交付物从第 0 轮就存在
|
||
|
||
### 事实
|
||
|
||
上游 `active_rectification_events.py:115-179` 有 `build_candidate_result_summary()`,产出四个面向用户的 next-step code:
|
||
|
||
```
|
||
collect_at_least_five_events
|
||
resolve_candidate_tie_or_narrow_window
|
||
narrow_window_before_minute_claim
|
||
do_not_apply_as_birth_time_truth
|
||
```
|
||
|
||
**Jyotisha 把这整段删了。** 于是访谈终止时没有话术锚点,只能继续问。
|
||
|
||
上游的降级阈值 Jyotisha 其实完整继承在 `references/rectification_policy.v1.json`(4 事件 / 3 域 / 5 分钟宽度 / 20% margin)——**判据没丢,丢的是「达不到时说什么」。**
|
||
|
||
### 要做什么
|
||
|
||
1. 把上游的 `build_candidate_result_summary()` 恢复到 `Jyotisha/scripts/active_rectification_events.py`,让 `scripts/rectification/api_service.py` 的 `score_candidates()` 返回体带上 `next_step_codes` 与 `stability.label`。
|
||
2. 定义**校正报告**投影,随时可读:结论区间 + 代表分钟(明确标注为代表性、非唯一解)、置信度、逐条证据(事件 → 支持哪个候选 → 用哪个方法)、被排除的候选与理由、局限声明(复用 `confirmation-gate` 的 blocker 文案,不要另写一套)。
|
||
3. **第 0 轮就存在**:证据 0 条时区间 = 用户声明的出生窗口。每答一题重算。
|
||
4. 用户任何时刻可查看、可导出、可主动结束。结束即交付当前版本,**不算失败**。
|
||
5. UI 给出区间收窄进度(`±120 → ±45 → ±18 → ±7`)。这是用户为这笔钱买到的东西的可视化。
|
||
6. 计费与交付对齐:产生过至少一版报告即为有效交付,`exhausted` 不是失败态。**具体金额由 `TASK-billing-pricing-20260830.md` 决定,本任务不写任何金额。**
|
||
|
||
### 验收
|
||
|
||
- 新 case 在零证据时即可读出一份报告。
|
||
- 每轮答题后区间单调收窄或不变。
|
||
- 用户主动结束或超预算 → 拿到报告,状态是完成不是失败。
|
||
- `next_step_codes` 出现在服务端返回体里,并被终止话术使用。
|
||
|
||
---
|
||
|
||
## 任务 4(P1)· 解决标定数据饥荒
|
||
|
||
### 事实
|
||
|
||
两边都没有盲测数据(事实 6)。Jyotisha 的 `v4_intake` `blind_holdout_eligible_case_count = 0`,上游 `ready_case_count = 0`。这是产品能不能承诺唯一分钟的唯一瓶颈,**且它是数据问题不是工程问题**。
|
||
|
||
上游停在这里六周了,指望它自己解决不现实。
|
||
|
||
### 要做什么
|
||
|
||
做一个**免费的「验证我们」入口**,面向已经知道自己准确出生时间(有出生证明/医院记录)的用户:
|
||
|
||
1. 用户提供出生日期、地点、事件;**准确时间单独收集并对打分链路屏蔽**,全程不得进入候选生成或打分。
|
||
2. 系统盲跑校正,出结果后再与真实时间比对,把偏差当场展示给用户。
|
||
3. 每一例都是一条合格标定数据,用户同时拿到一次免费的、可验证的能力演示。
|
||
|
||
一个入口同时解决三件事:标定数据来源、最有说服力的社会证明(「我们在 N 个有出生证明的案例上盲测过,中位偏差 X 分钟」)、获客内容。
|
||
|
||
### 硬约束
|
||
|
||
- **真实时间必须在架构上不可能泄漏进打分链路**,不是靠约定。写负向测试锁死。
|
||
- 案例默认进 intake 队列,晋级为冻结 holdout 必须走既有流程:新版本号、通过源审计、打分身份在盲测前冻结。
|
||
- 免费入口要有独立的滥用限制,不得复用咨询的公平使用配额。
|
||
- 与上游共享数据前先对齐口径,避免两边各建一套互不承认的标定集。
|
||
|
||
### 验收
|
||
|
||
- 真实时间泄漏的负向测试存在且通过。
|
||
- 新案例正确落入 intake 队列,不会被自动当成 holdout。
|
||
- 用户侧能看到「预测 vs 真实」的偏差对比。
|
||
|
||
---
|
||
|
||
## 任务 5(P2)· 清理与同步机制
|
||
|
||
**任务 1–3 上线并稳定运行两周后才做。**
|
||
|
||
1. **两代数据模型并存**:`birth_time_rectification_*` 17 张 + `agentic_rectification_*` 16 张 = 31 张,是 50 个校正迁移的主要来源。逐张确认旧表是否仍被 RPC 写入,无引用的归档删除。
|
||
2. **删掉永不执行的分支**:`method-followup.ts` 注释写明第 5 项(外貌体质)与第 6 项(胎记疤痕)`skipped; never asked`。
|
||
3. **`MIN_SEPARATION_LEAD` 与上游口径对齐**:`core/candidate-separation.ts:7` 当前是绝对分差 8;上游 `active_rectification_events.py:256` 用的是相对 margin(< 10% 降级)。改成相对量后用 `minute_rectification_holdout_v3` 的 20 例看 `not_separated` 比例变化 —— **只看趋势,不作发布指标**(红线 6)。
|
||
4. **建立与上游的同步机制。** 六周零同步是本轮很多问题的背景。约定一个节奏(例如每月一次),至少同步 `references/`、`scripts/active_rectification_*`、方法学 md。这不是代码任务,是流程约定,写进 `AGENTS.md`。
|
||
5. 每删一张表、每并一个模块单独一个提交,便于二分回滚。
|
||
|
||
### 不要动
|
||
|
||
打分引擎与 Swiss Ephemeris 计算、证据账本、Postgres 里的状态机、sealed holdout 机制、`rectification_policy.v1.json` 的阈值。这些都是对的。
|
||
|
||
---
|
||
|
||
## 任务 6(P0)· 接过上游的终止语义
|
||
|
||
### 事实
|
||
|
||
见任务 0 结论。任务 1 已接入轮次型预算,但上游的终止是**证据状态型**的,且 `exhausted` 在上游是正常终态而非兜底态。
|
||
|
||
### 要做什么
|
||
|
||
1. **增加证据型停止规则**,与既有轮次预算并列(任一命中即停并交付):
|
||
- 带日期事件 < 3
|
||
- 覆盖 domain < 2
|
||
- 并列第一(已有 `separation` 可判)
|
||
- 用户不确定度过高,无法可靠映射 support/conflict
|
||
|
||
阈值取 `references/upstream/evidence_thresholds.md` 的 Minimum Standalone Gate(3 事件 / 2 域),**不要沿用本仓的 4/3**——那组是确认门,语义不同,两者必须分开保留,不得互相覆盖。
|
||
|
||
2. **把 `exhausted` 从兜底态重新定位为正常终态。** 改命名与全部用户可见文案:走到这里是「交付完成」,不是「很遗憾没能完成」。计费上它必须是有效交付(与 `TASK-billing-pricing-20260830.md` 对齐)。
|
||
|
||
3. **引入上游的 label ladder**,替换「确认唯一分钟」作为默认目标:
|
||
`blocked → user_history_verification_required → manual_pattern_consensus → single_adapter_support → multi_adapter_consensus`
|
||
唯一分钟确认降级为 sealed holdout 通过后才开启的路径,**不得作为访谈的默认终点**。既有 `confirmation-gate.ts` 的 blocker 保持不变(红线 2),它现在正确地永不放行。
|
||
|
||
4. **采用上游的终止话术**,逐字使用任务 0 结论第 4 条那句。它是上游打磨出的合规表述,不要另写。
|
||
|
||
5. **收紧提问节奏到三轮形态**:第 1 轮 A/B/C/D 选择题批量出,第 2 轮只要最强 domain 的带日期事件,第 3 轮只问能区分 top 候选的 1–3 个。当前 8 轮预算作为**外层熔断**保留,不作为正常节奏。
|
||
|
||
### 验收
|
||
|
||
- 证据型停止规则有单测:3 事件以下、2 域以下、并列第一各自触发终止并交付区间。
|
||
- 4/3 确认门与 3/2 交付地板在代码中是两个独立常量,不互相覆盖,注释写明语义差别。
|
||
- 用户可见文案中不存在把正常终态描述为失败的表述。
|
||
- 默认路径下系统不再以「确认唯一分钟」为目标;相关文案与 label 均出自新梯子。
|
||
- 既有校正测试与任务 1 的四条属性测试全绿。
|
||
|
||
---
|
||
|
||
## 任务 7(P0)· 逐条回答 → 区间收窄的归因表
|
||
|
||
### 事实
|
||
|
||
`references/upstream/1993-session-correction-rerun.txt` 里,整场对话最有价值的产物是用户主动索要的那张表:
|
||
|
||
| 你的回答 | 压缩后的分钟区间 | 作用 |
|
||
| --- | --- | --- |
|
||
| 2017 入职,岗位内容不合 | 14:48–14:50 | 很强的收窄点 |
|
||
| 2023 末起量,一改风格就稳 | 14:48–14:50 | 最接近最终分钟的一条 |
|
||
|
||
本仓没有这个概念。`v9/evidence-model.ts` 与 `core/*.ts` 中不存在 `narrow` / `contribution` / `attribution` 语义,只有「这条证据支持哪个候选」,没有「这条回答把区间从 X 压到了 Y」。
|
||
|
||
### 要做什么
|
||
|
||
1. 每次证据写入后记录**区间快照**:写入前宽度、写入后宽度、以及该条证据的归属贡献。持久化,不靠事后重算。
|
||
2. 报告(任务 3)中新增归因表:用户原话(`display_date_label` 口径)→ 收窄前后区间 → 一句作用说明。
|
||
3. 收窄贡献必须来自服务端打分,不得由模型叙述生成。模型只做措辞。
|
||
4. 无贡献的证据也要列出并标注「未产生收窄」——这本身是诚实性的一部分。
|
||
|
||
### 验收
|
||
|
||
- 报告含归因表,条目数等于已确认证据数。
|
||
- 每条的收窄前后宽度与该轮实际候选集一致,可复核。
|
||
- 零贡献证据被显式标注,不被静默丢弃。
|
||
|
||
---
|
||
|
||
## 任务 8(P1)· 「为什么是这个结论」的质疑通道
|
||
|
||
### 事实
|
||
|
||
会话记录里用户问了一句「你是信息记忆联想了吗?还是靠印度占星专业技术推理出来的?」——这一问改变了整场对话,也是全部真相的来源。
|
||
|
||
本仓没有任何「为什么」入口。`v9/choice-card.ts` 的 `why` 字段是每个选项的说明,不是「这个结论怎么来的」。
|
||
|
||
### 要做什么
|
||
|
||
1. 结论旁提供一个显式入口,回答三件事:用了哪些证据、用了哪些方法层(dasha / D9 / D10 / D12…)、当前还有哪些 blocker 未解除。
|
||
2. 内容全部来自服务端已有的 `claimCards` / `evidenceRefs` / `confirmation-gate` blocker,**不得由模型即兴生成**。
|
||
3. 必须能诚实回答「这个结论有多少来自你的回答」——若某个结论主要由先验或默认值决定,要说出来。
|
||
|
||
### 验收
|
||
|
||
- 入口可达,内容可复核到具体证据 ID 与方法层。
|
||
- 关闭 blocker 前后,解释内容随之变化。
|
||
- 解释文本中不出现内部标识符(与任务 2 的输出面约束一致)。
|
||
|
||
---
|
||
|
||
## 任务 9(P1)· 中途改窗口与保留证据重跑
|
||
|
||
### 事实
|
||
|
||
`1993-session-correction-rerun.txt` 整篇的主题就是「默认只把 14:30–15:00 当作未知区间,重新跑一轮」。
|
||
|
||
本仓 `declared_window` 在开 case 时从 profile 读一次(`v9/case-service.ts:42-43`、`rectification-agentic/session.ts:194-195`),未找到中途重定义或保留证据重跑的路径。这是高频真实场景:家人过后又想起更准确的时段。
|
||
|
||
### 要做什么
|
||
|
||
1. 支持在 case 进行中重新声明窗口,并基于**已有证据**重新扫描打分,不要求用户重答。
|
||
2. 重跑必须留痕:旧窗口、新窗口、触发原因、重跑前后的候选与区间,进 receipt。
|
||
3. 与计费对齐:同一 case 内重跑**不重复扣费**(沿用 `rectification:case:<caseId>` 幂等键)。
|
||
4. 若新窗口与既有证据严重冲突,如实呈现冲突,不得静默丢弃证据。
|
||
|
||
### 验收
|
||
|
||
- 改窗口后候选重算,已确认证据全部保留。
|
||
- 重跑留痕可查。
|
||
- 重跑不产生第二次扣费。
|
||
|
||
---
|
||
|
||
## 任务 10(P1)· 第一轮批量出题
|
||
|
||
### 事实
|
||
|
||
`src/mastra/agentic-rectification.ts:68` 第 6 条硬编码「一次一问」。
|
||
|
||
上游 `references/upstream/interview_playbook.md` 的节奏是第 1 轮 **3–5 道 A/B/C/D 一起出**,会话记录里也是「你直接回 A/B/C 就行」。一次一问 × 8 轮摩擦大、轮次消耗快,**直接加剧不收敛**。
|
||
|
||
### 要做什么
|
||
|
||
1. 第 1 轮改为批量出 3–5 道 A/B/C/D 选择题,一次呈现,用户可逐条或一次性作答。
|
||
2. 第 2、3 轮维持单问(只要最强 domain 的带日期事件 / 只问能区分 top 候选的 1–3 个)。
|
||
3. 系统提示第 6 条随任务 2 一并重写;批量题的题干与选项由服务端产出(与任务 2 的问题槽一致),模型不参与。
|
||
4. 批量轮在轮次预算里**记为一轮**,不是 3–5 轮。
|
||
|
||
### 验收
|
||
|
||
- 第 1 轮呈现 3–5 题,作答后进入单问节奏。
|
||
- 轮次计数正确。
|
||
- 与任务 1 的四条属性测试不冲突。
|
||
|
||
---
|
||
|
||
## 交付
|
||
|
||
- PROGRESS 写进 `PROGRESS-rectification-convergence-20260830.md`,任务 0 的会话记录结论单列。
|
||
- 每个任务一个提交。
|
||
- 推 staging 前:`./node_modules/.bin/tsc --noEmit` 通过、`npm run lint` 无新增 error、`npm run test:db` 在 Docker 下 fail=0、`npm run build` 通过。
|
||
- 全量 `./node_modules/.bin/tsx --test tests/*.test.ts` 的失败清单与基线逐条比对,确认无新增。
|