Files
Jyotisha/docs/tasks/PROGRESS-rectification-precision-gate-guided-collect-fix-20260916.md
T
jesse-ux 05cf20fb83
Independent Staging Quality Gate / validate (push) Successful in 14m41s
Independent Staging Quality Gate / publish (push) Successful in 40m10s
fix(ci): capability audit 丢掉 Next.js 路由组,公开 URL 不变
Gitea run 2697 唯一失败:侧栏把 /chart /ephemeris /reports 移进 app/(secondary)/ 后,_scan_app_routes 把文件夹名写进 app_routes。扫描时丢掉括号段。BUG-753。
2026-09-16 21:09:24 +08:00

192 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 进度 · 出卡精度门槛 + 引导式补经历(验收修复单,2026-09-16)
- 任务书:`docs/tasks/TASK-rectification-precision-gate-guided-collect-fix-20260916.md`F1F7 + 决策 D6
- 基线:`origin/staging` @ **`a396bbe0`**(第二轮已 rebase;上一版基线 `302ff085`,代码基线 `cfb41daf`
- 分支 / 工作树:`codex/rectification-precision-gate-guided-collect-fix-20260916` / `.worktrees/rectification-precision-gate-guided-collect-fix-20260916`
- BUG**747753**。开工时最大号是 743;实现期间另一会话把侧栏统一那一单合入 staging,占掉了 744~746,所以本单整体后移到 747 起。rebase 后 `docs/BUG_HISTORY.md` 里 743 → 744 → … → 752 连续。合入后 Gitea run `2697` 仍红一条 capability audit,补 BUG-753。
- 开工检索(AGENTS §5):BUG-733、654、656、740743 已逐条读完;BUG-733 的防复发条本轮补了一句(见下)。
## 0.2 合入后跟进(BUG-753Gitea run 2697
`dc732825` 把 F1 修绿(797 passed),但 run `2697``1 failed``test_capability_audit_scans_registry_and_local_sources` 扫到 `(secondary)/chart` 等。侧栏把四个次级页移进路由组后,`_scan_app_routes` 把文件夹名当成公开 URL。扫描时丢掉 `(…)` 段,期望列表仍是 `chart` / `ephemeris` / `reports`。本地 `pytest` 该条 + growth contract 8 passed。
## 0. rebase 到 `a396bbe0`
第二轮已 `git rebase origin/staging`(侧栏统一那 4 笔:`d9d34723``fb77c865``22edabbc``a396bbe0`)。
- **自动合并**`frontend/DESIGN.md``frontend/src/app/page.tsx`。page.tsx 以对方的结构为准,只保留本单那一处 `rectificationDeclaration` 合并(`declaredTime` + `birthDate` 一个对象展开)。
- **手工解冲突 3 个**,都在文档、都是「两侧各自追加」:
- `CHANGELOG.md`:两条都保留,本单那条在上(后写的在前)。
- `docs/BUG_HISTORY.md`:以 staging 版本为底,重新插入本单对 BUG-733 的那一行补注,再把本单的 BUG-747~752 整块追加到 BUG-746 之后。结果 `## BUG-` 共 739 条,末尾 743 → 744 → … → 752 连续。
- `docs/tasks/README.md`:侧栏单那一行取对方的(已验收状态),本单那一行取自己的;两行都在。
- BUG 编号 **747752 不变**744746 已被侧栏轮占用)。
## 0.1 结论先行(全部在 `a396bbe0` 上重测;基线也在 `a396bbe0` 上用一次性 worktree 重跑)
| 项 | 基线 `a396bbe0` | 本分支 |
| --- | --- | --- |
| `tsc --noEmit` | 0 错 | **0 错** |
| `npm run lint` | 0 error / 115 warning | **0 error / 115 warning** |
| `npm test` | 3399 条 / **47 红** | **3405 条 / 31 红**;31 红与基线逐条同名,**0 条新红**,基线 47 红里的 16 条全部转绿 |
| `pytest tests/test_rectification_*.py tests/test_event_probes_guided_windows.py` | — | **193 passed** |
| `next build --webpack` | `○ /` | **`○ /`**`/chart``/ephemeris` 也仍是 Static |
| 首屏 gzip | 583,765 | **582,800**(−965 B**0.17 %** |
| `frontend/src/app/page.tsx` | 1836 行 | **1835 行**(−1,未增长) |
| Skill | 10.0.27 | **10.0.28**sha256 `f0bb8295…cffe` |
## 1. F1 · 记忆化 goldenBUG-747
- `tests/golden/rectification_engine_memoization_v1.json` 只追加 `guided_collect_windows` 一个键。`git diff --stat` = **`1 file changed, 62 insertions(+)`**`git diff` 里只有一段 `+ "guided_collect_windows": [ … ]`,插在 `discriminating_event_probes` 之后(与 live receipt 的键序一致),CRLF 行尾保持不变。**没有删除或修改任何既有行。**
- 未放宽 `_assert_tiered_equal`(键集合仍是严格相等),未调用 `write_golden()`
- 顺带修了一个同族隐患:`_golden_payload()` 现在把 `scripts.rectification.event_probes``date` 换成 `_FrozenDate``today() == FROZEN_TODAY = 2026-09-16`)。窗口年份来自 `date.today()`,不冻结的话这份 golden 会在跨年时自己变红。冻结后生成的值与本机实跑一致,所以 golden 内容没有因此改变。
- `docs/BUG_HISTORY.md` 的 BUG-733 条目补了一行「2026-09-16 因 receipt 新增键重生成,diff 仅此一键」。
## 2. F2 + F3 · 断言处置清单(BUG-748 / BUG-751
先按 F3 改代码,再逐条看断言。**没有删任何测试,没有 skip 任何测试。**
### 2.1 代码改动(F3 + D6
`core/rectification-decision.ts`
```
mayDeliverOnPrecision(input):
userStopped === true → true
precisionGateMet 与 guidedCollectExhausted 都缺省 → true(门槛不参与,helper 路径回到 317e9f18
否则 guidedCollectExhausted === true
&& refreshExhausted !== false
&& targetedCollectExhausted !== false = 所有题源都问完)
```
**口径(D2 + D6,第二轮修正)**:门槛只在**还有题可问时**挡住出卡。题源全空就是 D2 说的「用户确实补不出来」,卡片、采用按钮、三句正文照现行规则给,不加「未达门槛」标注。
写全是 `userStopped || allExhausted || (precisionGateMet && allExhausted)`,化简后就是 `userStopped || allExhausted`
**按 D2 + D6,10 分钟门槛不改变出卡时机,本轮实际生效的是题源变多(引导窗口题 + 跳过线重问 + 七条线全部轮到)、线问完才出。**
`precisionGateMet` 保留计算,并新挂到 `RectificationDecision.precisionGateMet``publicNextAction().precision_gate_met`helper 路径不传 flag 时为 `null`),只上报、不决定时机。
撤回的三处短路(全部回到原判断):
1. `coverageBlocks` 分支的 `const narrowingOpen = stillNeedNarrowing(input) && !mayDeliverOnPrecision(input)`
2. `!separation.sufficient` 分支的 `if (stillNeedNarrowing(input) && !mayDeliverOnPrecision(input))`
3. `if (input.datedMethodCollectOpen === true && !input.userStopped && !mayDeliverOnPrecision(input))`
保留门槛的五个最终交付分支:`tied_first` 耗尽、`!sufficient` 的 exhausted / adopt_representative / offer、顶层 exhausted。
`v9/decision-from-dossier.ts``narrowingExhaustion``guidedCollectExhausted` 改为 `refreshDone && guidedCollectExhausted(...)`,即引导池空但刷新没试过不算问完。
新增三条单测(`rectification-precision-gate-20260916.test.ts`):
| 单测 | 断言 |
| --- | --- |
| `empty guided pool with refresh untried does not deliver` | `refreshExhausted: false` + 池空 + 门槛达标 → `ask_fact_collection``canOfferRange=false``mayDeliverOnPrecision` 直接返回 false |
| `gate met but the targeted line is still open keeps asking` | `precisionGateMet: true` + `targetedCollectExhausted: false``ask_fact_collection` |
| `every line asked but the gate unmet still delivers under the existing rules` | 池空 + 门槛未达 → `canOfferRange=true``sessionOutcomeAllowsDelivery=true`,且 `precisionGateMet` 仍报 `false`(D2:题源空了就按现行规则出卡,不加标注) |
| `the precision gate is reported on a collect exit too` | 采集出口上 `decision.precisionGateMet === false``publicNextAction().precision_gate_met === false`helper 路径(不传 flag)为 `null` |
### 2.2 十六条基线红(任务书 §2 F2 表)
| # | 文件 :: 用例 | 处置 |
| --- | --- | --- |
| 1 | `adopt-flow-fix-20260903` :: collect-phase nonterminal exit… | **改断言(三栏)**`adopt_representative/offer_provisional_range/publicCanAdopt=true``collect_evidence/ask_fact_collection/false`;随之 `repaired.persisted` false→true、repair 警告 false→true、`store.focus` null→非空。D113 分钟、14:13:13 并列)+ D6,本用例回到 BUG-648 之前的采集出口,与题名一致 |
| 2 | `adopt-flow-fix-20260903` :: skipped opening collect other still yields education… | **改断言(三栏)**`plan.next_followup = null` → 学业线 `collect:targeted:education`;下半段 `educationPlan.next_followup.domain` `horary``education`。D5 七条线全部轮到,题名要的「yields education」正是这一条 |
| 3 | `adopt-narration-20260904` :: fourteen-probe case decides offer_provisional_range… | **仅改代码即恢复绿**(F3 撤回短路后 helper 路径回到原语义),断言未动 |
| 4 | `adopt-narration-20260904` :: family collect declined vs extra distinguish declined… | **改断言(三栏)**`/搬家|换城市|还能把剩下的候选分开/` → 两侧都匹配 `/现在还剩 .+ 里 \d+ 个候选/`。D5 之后迁居已覆盖,剩下的线是财务与健康;题名要的「两侧同一个 adopt 决策、都落库」不变 |
| 5 | `answer-choice` :: answering the last discriminator persists a year-locked family collect focus… | **改断言(三栏)**`setFocus === undefined` → 写入 `collect:targeted:education`;末条「没有 focus 带 `p_asked_turn_id === TURN_ID`」改成「写入的 focus 只能是这道新问题」——`linkFocusAskedTurn` 把新问题挂到本轮刚追加的 assistant turn,而夹具让这个 id 恰好等于 `TURN_ID`,旧断言在这个夹具里已无法区分两者 |
| 6 | `choice-card` :: family coverage without a renderable discriminator still collects the next dated domain | **改断言(三栏)**`plan.next_followup = null` → 迁居线 `collect:targeted:relocation`。题名要的「still collects the next dated domain」正是这一条 |
| 7 | `collect-direction-20260904` :: holdout remaining domain uses the server collect stem… | **改断言(三栏)**`choice_frame === null``choice_kind === "existence"``USER_COLLECT_QUESTION.education``TARGETED_EXISTENCE_PROMPT.education`,另加一条「不得是反向核对口吻」的断言,保住题名的原意 |
| 8 | `collect-direction-20260904` :: four scoreable events stop dated collect… | **改断言(三栏)**`intent != collect_method_evidence``== collect_method_evidence``collection_key` 必须是 `collect:(targeted|guided):`(走定向七条线,不是旧的领域轮盘) |
| 9 | `collect-direction-20260904` :: separated candidates stop leftover dated collect once the training gate is open | **改断言(三栏)**`sessionOutcome != collect_evidence``== collect_evidence``nextAction = ask_fact_collection``userAction.id = ask_method_followup` |
| 10 | `convergence-budget` :: dossier and post-inference decisions share the half-uncertain stop rule | **改断言(三栏)**`stopReason = "probe_pool_exhausted"``null`,并显式断言两条路径的 `stopReason``nextAction` 一致(题名要的「share the rule」) |
| 11 | `decide-next-action` :: separated candidates stay in collect while dated method collect is still open | **仅改代码即恢复绿**,断言未动 |
| 12 | `delivery-vs-collect-20260914` :: POST idle persist and GET decideFromDossier agree on a first-place tie | **改断言(三栏)**`getDelivers = true``false`(D1 并列时门槛恒假);「POST 与 GET 同口径」那条不变 |
| 13 | `superseded-focus` :: duplicate_focus on the last collect ask does not emit delivery copy | **改断言(三栏)**`p_target_domain != relationship` → 改断言写入的是 `collect:(targeted|guided):` 问题(夹具本身叫 `relationshipOpenDossier`,这条线是开着的);`!= finance` 与「不得播交付文案」保留 |
| 14 | `targeted-collect-cards-20260913` :: A resolves existence then asks the year; B/C close only that line | **改断言(三栏)**:题干 `结过婚或订过婚吗?``TARGETED_EXISTENCE_PROMPT.relationship`T4b 问整个领域);`targetedCollectExhausted` 从「拒答 4 条即耗尽」改成「4 条 → false、七条 → true」 |
| 15 | `targeted-collect-cards-20260913` :: personality D9/D10 stay out of the scoring pool… | **改断言(三栏)**`narrow_hint``不限领域` → 不得含 `不限领域`,改为匹配引导句 `现在还剩 … 里 N 个候选`D3 删自由文本邀请) |
| 16 | `tied-first-fix-20260914` :: tied first place with no probe still collects while targeted questions remain | **仅改代码即恢复绿**,断言未动(这正是 F3 说的 BUG-654 规则回来了) |
### 2.3 D5/D6 连锁打红的另外 25 条(任务书未列,本轮一并处置)
撤回 `datedMethodCollectOpen` / `stillNeedNarrowing` 两处短路之后,cfb41daf 的 T4b`remainingTargetedDomains` 不再按分盘层剔除,七个 `CollectKind` 全部轮到)就重新挡住了交付——这是 D5 + D6 的**预期**结果,但 cfb41daf 用短路把它盖住了,所以这 25 条在基线上是绿的。全部按「原值 / 新值 / 原因」三栏改:
| 文件 | 条数 | 处置摘要 |
| --- | ---: | --- |
| `coverage-collect` | 4 | `offer_provisional_range` / `ready_to_adopt``ask_fact_collection` + `collect_evidence``canAdopt` 仍是引擎能力位,保持 true |
| `provisional-adopt` | 6 | 公开投影 `can_adopt` / `session_outcome` 翻到采集侧;`canRenderRectificationSelectionCards` true→false`buildNextUserAction` `adopt_representative``ask_method_followup``selectionAllowed` true→false |
| `range-offer-deadend` | 2 | `ready_to_adopt``ask_fact_collection``decision.canAdopt` true→false(零拆分探针被 drop 的断言保留) |
| `eight-method` | 1 | `rectification-offer-candidates` 由「可走」改为 `assert.rejects(/offer_not_allowed/)` |
| `collect-direction-20260904` | 1 | 同上(offer-candidates 仍被挡) |
| `exhaustion-exit-20260906` | 1 | `adopt_representative``collect_evidence``stopReason`→null、旁白由三句交付改为引导句 |
| `occupation-coverage-exit` | 1 | `canOfferRange` true→false、`collect_evidence` / `ask_fact_collection` |
| `holdout-renderable` | 1 | `offer/ready``ask_fact_collection``validated_range` 断言保留 |
| `collect-stall` | 1 | 只改夹具:补齐 education / career / relationship 三条拒答(D5 之后七条线都要轮到),断言全部保持原样(`publicCanAdopt=true``persisted=false`、不写 focus |
| `adopt-flow-fix-20260903` | 1 | `adopt_representative nonterminal exit…`:第二轮修正口径后无需改动,夹具与断言均恢复原样 |
| `answer-choice` | 1 | `last structured choice…``ready_to_adopt` / `can_adopt=true``ask_fact_collection` / false(这条的原因是 D5 的线没问完,不是门槛) |
| `decision-authority` | 1 | 公开字段深比较补 `precision_gate_met`(BUG-692 要求不得改成部分匹配,所以补字段而不是放宽比较) |
| `probe-pool-exhausted-20260911` | 1 | T3 第二轮修正后恢复原样;T4 只把末条旁白正则补上引导句(`persistNextInterviewIfIdle` 自己重算决策,写的是采集侧区间旁白) |
| `open-collect-invite-20260914` | 0 | 第二轮修正口径后恢复原样,无改动 |
| `surface-contract` | 1 | `declaredTime={rectificationDeclaredTime}``{...rectificationDeclaration}`F7 |
| `ingest-p0` / `delivery-report-facts` / `tie-break-entry-20260913` / `v9-contracts` | 4 | Skill 版本 10.0.27 → 10.0.28 |
另有 10 个文件只是 `RECTIFICATION_SKILL_VERSION` 常量断言随 bump 改数字(`collect-stall``confirmation-gate``delivery-report-facts``eight-method``exhaustion-exit``ingest-p0``occupation-coverage-exit``range-offer-deadend``replay-20260911``spoken-collect``v9-agent``v9-contracts``v9-entry-routing``window-cluster-cap``yearless-probe-downgrade``skill-registry`),每处都带一行三栏注释。
### 2.4 最终红清单
`npm test` fail=31,与基线 31 条**逐条同名**:无 Docker 的 database / deployment / staging 套件,以及 `[eval]` 路径别名那一组。
测试总数 3399 → **3405**(+6,全部是新增;**没有删除、没有 skip**):精度门槛 5 条(引导池空但刷新未试不交付、门槛达标但定向线未问完继续问、题源全空即使门槛未达也照现行规则出卡、采集出口也上报门槛、helper 路径上报 null+ F4 的 3 条 + F6 的 1 条,其中 2 条是把既有用例改写后新增的姊妹条。
## 3. F4 · 无领域轨道的窗口改成开放题(BUG-749)
- `scripts/rectification/event_probes.py`:删掉 `fallback[unlayered_index % len(fallback)]` 轮询;`vim` / `nara` 轨道的窗口 `domain = GUIDED_ANY_DOMAIN = "any"``nara:d9` / `nara:d10` 保持 `relationship` / `career`,该领域被拒答时也退回 `"any"``declined_domains` 现在只用于剔除例子与 d9/d10 的固定领域。
- 前端:`GUIDED_ANY_DOMAIN` / `GuidedWindowDomain` / `normalizeGuidedWindowDomain` / `guidedAnyExamples` / `firstOpenCollectKind``guidedWindowPrompt(window, openDomains)``any` 写「YYYY 年 M 到 M 月之间,有没有什么事,比如<口语 ≤3 个>?」,口语从 `KIND_ORAL` 表取、剔除 declined 与已覆盖,**没有按领域写 if**;全部领域都关掉时退化成「有没有什么事?」。`windowAlreadyAsked` 改成只比对(年, month_lo, month_hi),换个领域标签不重问。A 之后 `pendingGuidedYearWindow``any` 解析成第一个仍开放的领域,录入卡芯片默认落在它上面(`resolveEntryDomain` 另有防御)。
- 单测:Python 7 条(真实引擎 20 分钟窗 golden 非空、无领域轨道 = `any`、d9/d10 保留固定领域、拒答领域退回 `any`、split 均衡排序、跨年拆分);前端 3 条(开放题干口语 ≤3、同一窗口只问一次、芯片默认第一开放领域)。
## 4. F5 · 离线回放改成注入真值方向(BUG-752)
`scripts/research/guided_collect_holdout_replay.py` 新增 `window_boundary_dates()`:按 `guided_collect_windows` 自己的循环重算(轨道, index),拿回**每个候选**在该窗口上的边界日期。`truth` 组注入真值候选自己的那个月,`opposite` 组注入「剩余候选里离真值最远的那一个」的同一边界。三档半径都跑,`minute_rectification_holdout_v4.json` 20 例,**351.6 s,错误 0**,未用让步(没有只跑 ±10/±30)。
| 半径 | 方向 | 20 例达标 | 其中六题后本来就达标 | **靠引导件补上** | 达标件数中位 | 每例窗口数中位 |
| --- | --- | ---: | ---: | ---: | ---: | ---: |
| ±10 | truth | 3 | 3 | **0** | 0 | 6 |
| ±10 | opposite | 3 | 3 | **0** | 0 | 6 |
| ±30 | truth | 1 | 1 | **0** | 0 | 6 |
| ±30 | opposite | 1 | 1 | **0** | 0 | 6 |
| ±60 | truth | 0 | 0 | **0** | — | 6 |
| ±60 | opposite | 0 | 0 | **0** | — | 6 |
原始表 `docs/research/guided_collect_holdout_2026_09_16.json`(含每例的 `injected`,形如 `19:24:2026-05-08`,可核对注入确实落在不同候选的边界上)。
口径说明与观察:
- 上一轮「0/20」的写法**不成立**(注入的是窗口左端点),这一轮按真值边界注入之后**结论仍然一样**:没有一例是「六题后未达、补引导件后达标」。这次的数字可以作数。
- truth 与 opposite 两组数字完全相同,说明在这个 holdout 上,把事件放在真值的边界还是放在最远候选的边界,对 D1 门槛没有可测差别。
- 每例都有 6 个窗口(上限就是 6),所以不是「题不够」。
- 窗口年份集中在 2025–2026:排序先按 split 均衡、再按靠近今天,`lo = 出生年 + 5``hi = min(今年, 出生年+80)`。真人补经历时这几年确实最容易记得,但对名人 holdout 来说这些年份多半没有可用事件。
- **这不是合入门槛**,产品据此决定 10 分钟门槛是否要调,或者引导题源是否还要换一条路。
## 5. F6 · 录入卡提交文本(BUG-750)
`formatEventDateEntryMessage` → 「YYYY 年 M 月(D 日),<领域标签>方面有一件事」,标签取 `RANGE_DELIVERY_DOMAIN_LABEL`。单测断言两种日期形态与「不含『或』」。**没有**改成 `set-focus` / `batch` 的结构化写入(任务书给的是「或」二选一),走的仍是现有带年月证据路径。
## 6. F7 · page.tsx 与 golden fixture
- `page.tsx` **1838 → 1837**`rectificationDeclaredTime` 常量改成 `rectificationDeclaration = { declaredTime, birthDate }`JSX 里两行 prop 合成一行 `{...rectificationDeclaration}`
**偏离**:任务书写的是「把那一行挪进 `conversational-birth-time-rectification.tsx` 内部取档案」。做不到而且不划算——该组件没有档案来源:`RectificationCaseSnapshotPayload``case` 段只有 `status / accepted_time / confirmed_time / candidate_range / stage / birth_time_source`**没有 birth_date**;要在组件里拿档案得新开一次 `/api/account` 读取或改服务端合同,为了省一行代码多一次网络请求并不合算。行数目标已达成,记在这里由验收方判断。
- `tests/test_event_probes_guided_windows.py` 重写:新增 `_real_built()`,用 `build_candidate_static_context` 对公开 1990-01-01 北京测试盘的 12:0012:20(**20 分钟窗**,5 个采样分钟)跑真实引擎,断言窗口非空并逐字段校验;`test_declined_domains_are_removed` 改成 `test_declined_domains_fall_back_to_open_windows`,用真实引擎结果做,**`skipTest` 全部删除**(文件里已无 `skipTest`)。手造 static contexts 只留在排序与跨年两条纯结构断言里。
## 7. 记录
- `docs/BUG_HISTORY.md`:BUG-747752(本分支内连续;744~746 属侧栏统一单,见 §0),BUG-733 补一行。
- `CHANGELOG.md`2026-09-16 「问完所有线才出卡,时间窗不再硬贴一个领域」,写明 Skill 10.0.28。
- `frontend/DESIGN.md``guided-collect` 行改写(开放窗口题干、一窗一问、录入卡提交文本),`candidates` 行写明门槛是必要条件而非充分条件。
- `frontend/docs/VOICE.md`:新增两行(开放窗口题干、录入卡提交文本),已对照既有口径。
- `docs/tasks/README.md`:本单状态板改「已实现,待验收」+ 分支名 + 进度记录名。
- 真机清单沿用 `docs/testing/rectification-guided-collect-20260916.md`(六条),本轮没有新增浏览器级验收项。
- 未写入任何真实出生资料;示例只用公开名人 holdout 与 1990-01-01 测试盘。
## 8. 偏离与未做
1. **F7 的 `birthDate` 没有挪进组件内部**(理由见 §6),改用 props 合并把 page.tsx 降回 1837 行。
2. **(第一轮偏离,已修正)** 第一轮按修复单 F3 §2 的字面实现了「门槛未达即使线问完也只进 collect」,与主单 D2「引导题源也空了就按现行规则出卡」冲突,会造出「没有题也没有卡」的死角。产品第二轮拍板以 D2 为准:门槛只在还有题可问时挡出卡,题源全空照现行规则出卡。当前实现是修正后的版本,本记录各处数字也是修正后重测的。
3. **F2 实际处置 41 条而非 16 条**:任务书的 16 条表是在 cfb41daf 上数的,那一版用 `!mayDeliverOnPrecision` 短路盖住了 D5 带来的「七条线全部轮到」,撤回短路后另有 25 条既有断言跟着变。全部按三栏改,清单见 §2.3。
4. `run_quality_gate.py --profile quick` 的最后一级是 `npm test`,本机无 Docker,那 31 条与基线同名的失败照旧;前面各级(JSON、capabilities、fragments、BPHS、CORE_PYTEST_TARGETS)通过。
5. 浏览器级验收(登录态 / Chrome)、staging 部署核对仍是环境缺口,留给 `docs/testing/rectification-guided-collect-20260916.md`