diff --git a/research-wiki/designs/2026-07-15-question-gen-v3-construction-paradigm-design.md b/research-wiki/designs/2026-07-15-question-gen-v3-construction-paradigm-design.md
new file mode 100644
index 0000000..0128b86
--- /dev/null
+++ b/research-wiki/designs/2026-07-15-question-gen-v3-construction-paradigm-design.md
@@ -0,0 +1,297 @@
+---
+id: question-gen-v3-construction-paradigm
+title: question-gen v3 — 构造优先范式重构(全孪生 + 四家族真独立 + 坍缩度量)
+type: design
+created: 2026-07-15
+status: draft
+supersedes: 2026-07-15-question-gen-v2-grounded-contrastive-design.md
+---
+
+# question-gen v3:构造优先范式重构
+
+## 1. 目标与范围
+
+**完全重构**出题管线,**只做 Action Recognition 分支**。范式从 v2 的"自由生成候选池→打分过滤"(被 [2026-07-15-question-gen-paradigm-shift-construction-over-filtering.md] 判为 AFLite 死路)转向**"从视频树的结构化事实受控构造对比对,grounding/难度/唯一性由构造保证;难度/歧义用两个正交且真独立的信号验证;验收用配对坍缩度量而非原始准确率"**。
+
+**范围铁律**:v3 只产 AR 题(全部 pair 格式);11 个非 AR 题型仍走旧 `generate-v2`(single,byte-identical,零改动)。题库混格(AR pair + 非 AR single),评测/训练侧用 `QuestionUnit(single|pair)` 兼容。
+
+**决策锁定**(本次 brainstorming):① 全设计一次到位(6 子模式 + 完整契约);② 全孪生格式(含 fine_grained 改造成对比对);③ 全栈验证每题跑,不为成本砍;④ 四家族真独立裁判。
+
+## 2. 范式与文献依据
+
+依据 6 篇原文深读(`reference/papers-distractor-gen/`,证据页码见各深读报告):
+
+| 论文 | 移植的核心机制(本质,非表面) |
+|------|------------------------------|
+| Vinoground | 负项=正项**最小结构编辑**(bag-of-words 完全相同、只换单轴);grounding 靠真实时序事实;**双向 AND** 消语言先验;0 帧盲答对照 |
+| TempCompass | **冲突孪生对**(A 的干扰项=B 孪生体正解);**去捷径坍缩度量**(配对准确率掉回随机=有效性证明);多格式交叉一致性 |
+| VITATECS | **aspect 正交分解 + 单轴等信息量非蕴含最小编辑**;precision-over-recall;**警告:纯词汇/名词替换→74.5-90.3% 可解**(禁止) |
+| GroundAttack | 只换负项保 V/Q/A;grounding=独立度量真实视觉对齐;**软肋:wrongness 无独立验证**(必须外挂独立核验) |
+| AFLite | **过滤救不了质量**(单信号混淆难/歧义)→ 后置过滤只作兜底 |
+| AdVQA | **对抗前移生成端**(活求解器试答骗不过就重构)+ **独立于求解器的多裁判答案一致性**;自动对抗无核验→答案漂移 |
+
+**核心不变量**:① 干扰项=视频里真实存在、但绑到错误查询点的事实(grounded 且 wrong 由构造保证);② 答案核验器必须独立于被攻求解器;③ 度量用配对坍缩,非原始准确率。
+
+## 3. 架构与数据流
+
+```mermaid
+flowchart TD
+ T[视频树: 有序L1/L2/L3 + 结构化列表 + subtitle + frame_path] --> FS[fact_sampler 采可翻转结构化事实]
+ FS --> AX{子模式→孪生轴路由}
+ AX --> TB[twin_builder 构造孪生对 P/Q
单轴最小编辑, bag-of-words 硬匹配, 禁蕴含]
+ AX --> AB[attribute_binder 静态属性绑定
需薄抽取层: 树自由文本→对象,属性,位置]
+ TB --> L1[层1 结构构造保证: 相反事实锚定树节点]
+ AB --> L1
+ L1 --> L2[层2 坍缩度量: text-only盲答 + 单帧基线 掉回随机]
+ L2 -->|未坍缩| RJ[拒/重构]
+ L2 --> L3[层3 看帧核实: qwen+MiniMax 交叉查构造事实真伪]
+ L3 -->|安慰剂: 错配帧分不坍塌| RJ
+ L3 --> L4[层4 活求解器难度探针: deepseek AgentLoop 双向答对=太易]
+ L4 -->|太易| RJ
+ L4 --> L5[层5 独立多裁判歧义: kimi+qwen+MiniMax 三异家族判唯一性]
+ L5 -->|歧义/双正解| RJ
+ L5 --> L6[层6 多格式交叉: MC vs Y-N 求解器一致]
+ L6 -->|不一致=格式捷径| RJ
+ L6 --> ACC[on_accept 逐 unit 原子成对落盘]
+ RJ --> QN[失败内容指纹入 quarantine 去重] --> BF[补构造迭代
收敛上界+产量降级路径]
+ BF --> FS
+```
+
+## 4. 六子模式构造算法
+
+统一原理:**对锚定真实树事实的"正题"做单轴最小编辑得孪生"反题",干扰项=视频里真实存在但绑到错误查询点的事实**。
+
+**选项基数(契约锁定,§7 阈值全对齐)**:每个孪生子题 = **4 选 MCQ**(1 正解 + 3 干扰项),孪生 P/Q **共享同一 bag-of-words 选项集**(同词不同绑定),答案跨孪生翻转。
+
+所有孪生对硬约束:
+- **bag-of-words 硬匹配**(非"尽量")、单轴、等信息量、动词/事件结构编辑(**严禁名词/属性词汇替换**,VITATECS 实测改 1 名词即 82% 可解);
+- **禁蕴含**:正解与各干扰项互不蕴含,由 **kimi 真 LLM 裁判判定**(text-embedding 仅作 bag-of-words 词形辅助,**不得作蕴含唯一信号**——审核 C7:embedding 近似蕴含是假绿灯);
+- **平衡/因子布局根治逐维众数(C1 根治,非仅事后指标)**:4 个选项在每个语义维度上每个取值出现次数均衡(→逐维众数无定义),构造后跑 `majority_vote_baseline(options)==answer → fail` **纯算法硬校验**,作阶段 2 CI hard-fail;
+- **槽位强制反置**(防 C6 槽位偏置)+ **干扰项多样性**(3 个干扰项两两 embedding 距离须过下界门,防 GroundAttack distractor-similarity 扎堆一起被排除);
+- **题干模板对称(C8,防翻转轴泄露)**:before/after、first/last 题干本身会暴露翻转轴 → 题干须模板对称(不靠词面差异指示答案);**pair 关系可解率(P+Q 盲答+规则解法)作构造期 hard-fail**,不只事后验收指标。
+
+| 子模式 | 孪生轴 | 锚定树的什么 | 正/反题构造 | 干扰项来源 |
+|--------|--------|-------------|------------|-----------|
+| **temporal** | 时序换序 | L1 下有序 L2 序列或 L2 下有序 L3(`time_range`/`timestamp`,`children` 顺序=时序) | 正"X 之后是什么"→答真实后继;反 before↔after 翻转→答真实前驱 | 同视频真实存在但时序位置错的事件 |
+| **cross_segment** | 主宾互换/first-last | `action_subjects`+`actions` 跨 L2 段 | 正"谁**先**做 X"、反"谁**后**做 X",选项同集 | 别的段真实做该动作的主体 |
+| **premature** | 早/晚段锚点 | 段位**随机**(early/mid/late/final 各占,不固定 final) | 正问最终态、反问指定中间态 | 过早锚定的早段真实读数 |
+| **evidence_gap** | 诚实↔虚构因果 | 事件链 + `state_changes`(注意常 null,不可依赖) | 正支证据在场答=事实;反支证据缺席答="不可确定" | 树中未展示的合理因果延续(虚构但貌真) |
+| **semantic_rigidity** | 同义↔字幕陷阱 | 树的 `subtitle` 字段 + 视觉 `event_description` | 正题字幕与视觉真相一致;反题字幕是陷阱(匹配干扰项) | 字幕字面项(与视觉真相冲突) |
+| **fine_grained** | 同动作不同方式 | `ongoing_actions`+`visual_attributes`+看帧(需薄抽取) | 同一动作两个真实时刻的不同执行方式做对比 | 另一时刻该动作的真实不同方式 |
+
+> **事实来源澄清(对齐 §9)**:上表"锚定树的什么"列对 temporal 是**树结构直取**;对其余 5 子模式**仅为索引/候选提示**——真正的 grounded 事实由 **§9 帧感知抽取层从真实帧感知 + 双 VLM 交叉核实**得出(树的 `state_changes` 常 null、`actions/action_subjects` 无绑定等有损,均由帧感知绕过)。
+
+> **VITATECS 硬警告落地**:编辑必须落在**动词/事件结构**(时序/主宾/次数/方式),**严禁名词/属性词汇替换**(原文实测改 1 名词即 82% 可解)。每题构造后跑纯算法门校验单轴性 + bag-of-words 相等 + 逐维众数无定义。
+
+> **非 temporal 子模式的范式合法性论证 + 必达归属**(回应 finding §5 与 VITATECS 静态退化警告;构造机制见 §9 帧核实抽取层):
+> - **cross_segment / premature / semantic_rigidity = 必达**(§15 阶段2)。合法性——编辑轴均落在**事件结构**(主体-动作绑定 / 段位状态 / 视觉真相 vs 字幕字面),**非静态名词替换**,不落入 VITATECS 退化区;grounding 由 §9 帧核实抽取保证。semantic_rigidity 作为必达静态类,编辑轴=视觉真相 vs 字幕字面。
+> - **fine_grained = 实现+目标必达 + 运行时门兜底**:编辑轴是**动作执行方式(manner,属动词/事件结构轴:快↔慢、左手↔右手、单次↔重复)非静态名词**;grounding 由"两个真实时刻的方式对比"锚定。风险=未必总能找到两个真实对比时刻(产量)+ 方式差异是否够单轴等信息量。运行时合法性门不过则退 finding B 路线。
+> - **evidence_gap = 实现+目标必达 + 运行时门兜底**:"不可确定"选项**必须在正/反题对称出现**(防审核 C3"恒定文本靶");编辑轴=证据在场↔缺席(事件结构);幻觉风险最高,运行时门不过退保守构造。
+
+## 5. 四家族模型分工
+
+| 角色 | 模型 | .env 变量 | 家族 |
+|------|------|----------|------|
+| 活求解器难度探针(复用 `core/agent` AgentLoop) | deepseek-v4-pro | SEARCH_LLM | A |
+| 构造表面实现 + 看帧核实 | qwen3.6-plus | VL_LLM | B |
+| 独立看帧核实(异家族交叉) | MiniMax-M3 | M3_VL_LLM | C |
+| 独立歧义/唯一性 + **禁蕴含真裁判** | **kimi-for-coding** | **新增 JUDGE_PANEL_LLM** | D |
+| **仅 bag-of-words 词形辅助**(非蕴含判定) | text-embedding-v4 | TEXT_EMBEDDING | 纯算法 |
+
+> **禁蕴含/语义等价判定用 kimi 真 LLM 裁判**,text-embedding 只做"选项词袋是否相等"的词形辅助——**embedding 余弦不得作蕴含唯一信号**(审核 C7:测不出语义泛化蕴含,是假绿灯)。
+
+> 图像嵌入 `qwen3-vl-embedding` 暂不可用(几何跨模态 grounding 推迟);全孪生构造范式不依赖它。`kimi-for-coding` 需新增 .env 配置并**先测网关 newapi 是否服务**(实现第一步验证)。所有调用经 `GovernedLLMClient`(`adapters/llm.py`)治理,禁裸调。
+
+## 6. 六层验证栈(每题全跑,机制种类各异,不共享失效模式)
+
+| 层 | 机制种类 | 判据 | 失败处置 |
+|----|---------|------|---------|
+| **1 结构构造保证** | 符号 | 孪生答案=锚定树节点的相反事实,构造时即成立 | 构造非法→重构 |
+| **2 坍缩度量** | **模型探针→统计判据**(非纯算法,见 §7 分类) | text-only 盲答(deepseek,**输入含 subtitle** 以捕字幕泄答案)+ 单帧基线(VLM)正确率**必须掉回随机**(4 选≈0.25;pair 双向≈0.0625);超容差=可纯文本/单帧/字幕解→拒 | 拒+指纹去重 |
+| **3 看帧核实 + 帧错配安慰剂** | 视觉感知 | qwen+MiniMax 交叉查"构造相反事实真成立、干扰项在查询点真不成立";**安慰剂:喂错配帧分数须坍塌**(不坍塌=没真看帧)→拒 | 拒+重构 |
+| **4 活求解器难度探针** | 行为/对抗 | deepseek AgentLoop 对 pair 双向作答;双向都对=太易→拒(对抗前移,AdVQA) | 拒+补构造 |
+| **5 独立多裁判歧义核验** | 语义 | **多裁判 = kimi(D 文本)+ qwen(B 看帧)+ MiniMax(C 看帧)三异家族**,均独立于求解器(deepseek A);判是否有干扰项也成立/双正解,AdVQA 式一致性(无共识/多数判双真→拒)。text-embedding **不算裁判** | 拒+指纹去重 |
+| **6 多格式交叉一致性** | 跨格式 | 同一构造事实生成 **MC + Y/N 两格式**,求解器跨格式作答须一致;不一致=格式捷径而非真理解→拒(TempCompass 多格式交叉) | 拒+重构 |
+
+> **ABSTAIN 处置明确 + 终态闭环(防旧非对称谬误 + Codex I5)**:任一 VLM 探针"无法给有效帧引用"→记 `ABSTAIN` 入**分层人工抽检队列**,既不自动放行(防"引不出=放过")也不自动过杀。**必须有终态**:人工结论回写 `unit_verdict`(accept/reject);**超 SLA 阈值未标注按 fail 处理**(防无限 pending 吞产量)。
+> **难度/歧义两口径正交不复用**:难度=求解器答错(层4);歧义=独立裁判判唯一性(层5)。永不用同一次评分兼两职。
+
+## 7. 坍缩度量与反自证验收指标
+
+**主验收=配对坍缩**(TempCompass 度量革命):有效性证明不是原始准确率高,而是去捷径后掉回随机的幅度。
+
+> **指标三分类(I3,防 R4 自证误分类)**:**纯结构指标**(逐维众数命中率、答案槽位卡方、pair 关系规则解法、distractor 距离)——不依赖任何模型,是真正的算法地锚;**模型探针指标**(text-only 盲答、单帧基线、帧错配安慰剂、双正解率+κ、多格式一致率)——依赖模型输出的统计判据,**不得当"纯算法"背书**;**人工/抽检指标**(难/烂混淆矩阵)——分层小样本人工双标签。三类须分别标注,验收结论以纯结构 + 人工为硬地锚,模型探针为辅助信号。
+
+| 指标 | 靶向 | 手段(外部/纯算法/独立) |
+|------|------|------------------------|
+| 逐维众数命中率 | ②结构捷径 | 纯算法机械投票,阈值≈随机 |
+| 答案槽位卡方 + pair 槽位反置率 | ②C6 | 纯统计 |
+| text-only 盲答 / 单帧基线 掉回随机幅度 | 语言先验/单帧 | **模型探针**(deepseek 盲答 + VLM 单帧,非纯算法地锚,见上分类)|
+| 帧错配安慰剂坍塌幅度 | ①虚假 grounding | 错配帧分数须显著坍塌 |
+| pair 关系可解率 | ②C2 | P+Q 盲答+时序排序规则,须≈1/16 |
+| **subtitle_answerability_rate** | ②字幕泄答案 | 仅喂 subtitle+选项盲答,须≈随机 |
+| **distractor_similarity(多样性)** | GroundAttack 扎堆 | 3 干扰项两两 embedding 距离须过下界 |
+| **多格式交叉一致率** | 格式捷径 | MC vs Y/N 求解器答案一致率(低=格式捷径) |
+| 双正解率 + 裁判 κ | ③歧义 | kimi vs qwen/MiniMax(异家族),基线 33%→阈值≤5% |
+| 难/烂混淆矩阵 | ④/R4 | 分层人工抽检小样本双标签(可选锚点) |
+| quarantine_rate / backfill_yield_by_round / judge_disagreement_by_subpattern | R5/系统 | 观测落 run_store |
+
+阈值全部进科研 YAML + harness run 快照(可复现、可扫动);kimi/qwen endpoint 属工程 `.env`。
+
+## 8. QuestionUnit 契约(贯穿采样/分批/聚合,混格题库)
+
+`core/types.py` 新增 `QuestionUnit(single|pair)`:pair 载 `pair_id`/`twin_a`/`twin_b`/`flip_axis`/`unit_hash`/`collapse_metric`;`GeneratedQuestion` 加 `unit_id`/`pair_id`/`question_role`/`flip_axis`(默认值保非 AR single 不变)。
+
+> **选项基数契约(锁定,与 §4/§7 对齐)**:每个 `GeneratedQuestion`(含 twin 的 P/Q 子题)**仍是 4 选 MCQ + 字母答案**,AgentLoop/harness 的 MCQ 作答契约**不变**;pair 语义只是"P、Q 共享 bag-of-words 选项集、答案翻转、双向 AND 聚合"。多格式的 Y/N 子题作为**验证探针**(层6)——**独立类型、独立存储表、硬校验其不进 benchmark/pools/predictions、不复用 `GeneratedQuestion`**(M1),仅在构造期核验一致性后即抛弃,不改 MCQ 契约。
+
+**≥13 处改造入口**(复用审计 + 契约审核全表,v2 只覆盖 7 处,遗漏必崩):
+
+| 入口 | 文件 | 改造 |
+|------|------|------|
+| Global 三池切分 | `pools.py::build_pools`/`_sample_excluding` | 互斥单元 qid→pair_id,pair 同池 |
+| per-category 切分 | `pools.py::_split_one_category` | 输入改 unit,pair 不跨 train/val |
+| 分层采样 | `loader.py::stratified_sample` | 按 unit 计数,flatten 前 pair 不拆 |
+| batching 对错桶 | `batching.py::_select_mixed_by_task_type` | 按 **unit correctness(双向 AND)** 分桶 |
+| batching 整锁 | `batching.py::build_batches` | pair 像小类整组装箱,**同 batch 不拆**(否则坍缩算不出) |
+| 聚合 | `inference.py::run_inference` | pair 按 pair_id 收齐→pair-level record 双向 AND;unit 粒度 total/correct |
+| 序列化冻结/解冻 | `pools.py::_q_to_dict`/`_dict_to_q` + `save_pools.categories` | 显式写读 4 pair 字段 + 以 unit 记 train/val |
+| gate 阶梯 | `gate_ladder.py::build_cold_entries`/`ladder_for`/`update_probs` | entry 迁 unit_id + **schema_version + 迁移脚本**(存量 gate_pools.json 失效防硬崩) + 冷启动 2:1/gamma-EMA/Beta 先验按 unit 重定义 |
+| e-process | `core/evolution/validate.py::pair_block` | McNemar 臂配对按 unit(P AND Q 折叠后比对) |
+| correctness 消费(C3) | `runner.py`(rollout 回写 163-190 / accept 合并 1125-1142 / val 回写 1442-1454 / quadrant 分桶 / probation / momentum 1719-1760)+ `core/evolution/validate.py`(`pair_block` 12-37 / `compute_accuracy` 72-85) | 见下"correctness API 规格"——**逐一列出并改写这 8+ 调用点**,非仅"新增视图" |
+| **checkpoint/resume(C2,新增)** | `runner.py::epoch_batches`/`_batch_from_ids`(199-210/711-725) | 保存 **unit_id 序列**(非逐题 qid),恢复时展开完整 unit,防断点续跑后拆 pair |
+| **gate BaselineCache(C2,新增)** | `gate_ladder.py::BaselineCache`(277-335) | 键从 qid 改 unit_id,基线缓存按 unit 记忆,防 P/Q 分开缓存污染双向 AND |
+| 作弊门 | `adversarial_filter`→v3 难度探针 | pair 粒度判太易,禁半剔 |
+| 读回 benchmark | `loader.py::load_benchmark` | 补读 4 pair 字段(`.get` 兼容旧 JSON) |
+| 非 AR rng 隔离(I4) | `loader.py`(86-98/123-131/154-172 的 `rng.sample`)+ `pools.py`(604-612/811-815 的 split/shuffle) | **实现前先枚举所有 `rng.sample/shuffle` 调用点**;非 AR 抽样输入与 RNG 流固定,AR pair 折叠用**独立 seed namespace**,二者 draw 互不干扰 |
+
+**correctness API 规格(C3,三对象转换边界,非口号)**:显式定义三个对象及转换——
+- **逐题 predictions**(`question_id → prediction`):唯一溯源真相,rollout 逐题写。
+- **unit correctness**(`unit_id → bool`):AR pair = P.pred==P.ans **AND** Q.pred==Q.ans;非 AR = 单题。由 predictions 折叠得出,供进化/gate/quadrant/momentum/probation **统一消费**。
+- **pair collapse metric**(`pair_id → 坍缩指标`):验收用,不进 correctness。
+
+上表 8+ 调用点**逐一改写为消费 unit correctness**(含 `validate.py::compute_accuracy` 分母改 unit 数、`pair_block` McNemar 臂按 unit 折叠、quadrant 分桶按 unit_id);e-process 的 delta 信号在 unit 粒度计算,防 P/Q 单题计分污染进化。**先写此 API 规格 + 调用点替换清单,再动实现。**
+
+## 9. 帧感知 grounded 事实抽取层(一等公民,支撑全部 6 子模式)
+
+**这是让 6 子模式全部进入必达、又不滑回 v2 生成-过滤陷阱的关键机制。核心原则:树只是有损索引,真正的 ground truth 是帧本身——抽取直接喂真实帧,不依赖树的有损自由文本。**
+
+盘上每视频有真实关键帧(5 帧/L2 段,`store/videos//frames/`,L3 `frame_path` 定位);需更密帧时按 video id 重下原视频补采(有界局限,见 §16)。`grounded_fact_extractor`(新建一等公民组件):
+
+1. **帧感知抽取(VLM 对真实帧做感知,非对文本做解读)**:把目标段/时刻的真实帧喂 VLM,直接感知结构化事实——cross_segment 感知 `(主体,动作,段)` 绑定;premature 感知"各段状态读数";semantic_rigidity 感知"视觉真相"再与 `subtitle` 比;fine_grained 感知"动作执行方式"(左右手/快慢/次数);evidence_gap 感知"哪些证据视觉在场"。树的 `event_description`/`entities` 仅作**索引与候选提示**,不作事实来源。
+2. **双 VLM 交叉核实 + Fact schema(Codex C6)**:每条感知事实由 **qwen + MiniMax 两异家族 VLM 对同帧交叉核实**,落 **Fact schema**:`fact_id, subject, action, object, segment_id, frame_ids, polarity(真/假), verifier_refs, cross_agree, fact_type, difficulty_tier, negative_at_target`。**只有两 VLM 一致 + 多帧核实通过 + 绑定唯一的 fact 才进构造**;不一致/不唯一→丢弃。
+
+### 9.1 构造合法性硬约束(Codex 三审 C3/C5/C1/C4,缺一即滑回歧义/伪难题)
+
+- **判别性 + `negative_at_target` 排他(C5,最关键)**:干扰项**不是"任何真实事实绑错点"**——错绑定 ≠ 逻辑否定。必须:(a) 只选**判别性事实**(跨查询点/翻转轴会变的,非"表演者在台上"这类跨段持续/复现的事实);(b) 每个干扰项显式 `negative_at_target`——**帧核实它在目标查询点确为假**(闭世界证据或翻转轴互斥证明),不过则**判双正解 hard-fail**。重复场景视频(同一主体贯穿)大量事实非判别,须按此剔除(产量由 §10 降级兜底)。
+- **按 fact_type 的最小采样密度(C3)**:凡涉及**顺序/变化/次数/速度**的 fact(cross_segment 先后、premature 状态变化、evidence_gap 在场缺席、fine_grained 方式)——5 帧/段不足以稳定感知 → **触发密帧重采(按 video id 重下补帧)或降级**;不止 fine_grained。§9 为每个 fact_type 定最小密度。
+- **fact_type 难度分层(C1)**:粗粒度物体/姿态感知可靠,但**主体-动作绑定/快慢/计数/证据缺席是高阶判断**——这些 fact_type 打高 `difficulty_tier`,走**更严核实**(更多帧 + 人工小样本校准),禁止当普通感知放行。
+- **唯一绑定粒度 + 不唯一拆分(C4,防产量崩)**:定义唯一性粒度(同 segment/同 frame/同 action class/同 option set);多主体多动作段不唯一时**给拆分策略**(拆到可唯一的粒度再构造),而非一律丢弃。
+- **双 VLM 共享盲区校准(C2)**:两 VLM 一致**只降随机噪声、不排除同向幻觉**;加错配/遮挡/反提示一致性校准 + **人工小样本估 shared-error rate** 落验收面板;一致不当真值背书。
+
+### 9.2 实证验证状态(小样本 spike,见 [2026-07-15-v3-frame-perception-spike-validation.md])
+
+5 视频×3 段、94 次 VLM 调用 + Claude 亲自看帧核查,**机制已验证站得住**:可抽取率 100%、判别性 ~83%、negative_at_target 实证有效(正确抓双正解)、双 VLM 一致 87%、粗时序 5 帧可感知、抽样无双 VLM 一致幻觉。据实证细化 5 条(须折进实现):
+1. **fact_sampler 加"动作性"前置筛**(样本含幻灯片/录屏非动作视频,抽取虽成功但对 AR 无意义)。
+2. **fine_grained 密帧从"运行时兜底"升为"硬前置"**(实证:manner 5 帧确实不够,必须密帧重采或走 B 路线,不等运行时才发现)。
+3. **manner 类 fact 不靠双 VLM 交叉核**(实证一致性低)→ 走更严核实(更多帧+人工小样本),对齐 §9.1 C1 难度分层。
+4. **抽取主用详细但准确的 VLM(MiniMax 风格),保守 VLM(qwen)作交叉核**。
+5. 多主体绑定未被 spike 压测(样本多单主体)→ 见 §16 开放风险。
+
+**为何这不是 v2 陷阱、且比"从树文本抽取"更强**:v2 的失败是"VLM **判分**自由发明的干扰项"(判断题,不可靠);这里是"VLM **感知**真实帧里有什么"(感知/描述题,可靠得多)——干扰项始终是**从真实帧感知到的真实事实绑到错误查询点**,从不自由发明、从不循环打分。wrongness 由构造保证(真事实、错绑定),grounding 由**真实帧感知 + 双 VLM 交叉**保证(短于人工标注下最强)。
+
+**做不了**:bbox/像素坐标/精确计数/说话人 ID——放弃,不硬凑。
+
+**grounding 强度诚实分档**(不抹平):temporal = **结构保证**(树顺序,零模型依赖);其余 5 = **帧感知验证保证**(真实帧 + 双 VLM 交叉 + Fact schema,模型依赖但锚在真实视觉证据,非自由发明)。两档都是合法 grounded,来源不同,须在验收报告分别标注。
+
+## 10. 对抗前移、产量与续跑
+
+- **难度探针前移**:构造后、落盘前即用 deepseek AgentLoop 试答(对抗前移),不是事后过滤。
+- **失败题内容指纹去重**(R5 真正对症):对**题面语义内容**(非 question_id)算指纹,`拒/太易/歧义` 的内容指纹入 quarantine 黑名单;补构造命中黑名单即丢(堵"等价题换皮重试穿门")。
+- **收敛上界**:`max_total_constructed`/`max_backfill_per_slot`/`min_pass_yield`;连续 N 轮低产即停并报告。
+- **产量降级路径**(解产量危机,三选一写进配置):欠产(如 22/30)作为**带质量标签的正式交付物**;或 min_pass_yield 触发转分层人工抽检;明确**质量优先于数量**,`generate_ar30.sh` 的 TARGET 变软。
+
+## 11. 模块结构(Clean Architecture 四层)
+
+**在现有 `app/question_gen/` 内按领域模块替换/重组**(对齐 CLAUDE.md §4.2"直接改原文件、不留向后兼容"与 §5"禁版本化并行目录"——**不建 `app/question_gen_v3/` 并行目录**)。废弃模块(generator_v2/synthesizer/distractor_selector/gates/pipeline_v2)**原地删除或改写**,新增领域模块同目录落位:`fact_sampler`(采可翻转事实)、`constructor`(子模式→孪生轴路由)、`twin_builder`(孪生构造)、`grounded_fact_extractor`(§9 帧核实事实抽取,喂 cross_segment/premature/semantic_rigidity/fine_grained/evidence_gap)、`difficulty_prober`(活求解器,**不兼答案裁判**)、`ambiguity_verifier`(kimi 独立多裁判)、`collapse_metric`(结构/探针/人工三类验收,见 §7)、`pipeline`(主编排,替换 pipeline_v2);`run_store` 原地扩 pair 感知(不新建 run_store_v3)。
+
+**复用地基**:`adapters/*` 全部(GovernedLLMClient/VLM/telemetry/熔断/缓存)、`core/protocols`(扩 `JudgePanelProvider`)、`core/types`(扩 QuestionUnit)、`app/harness/*`(pair 契约适配,见 §8)、`run_store.py`/`loader.py`(续跑/加载基建)。
+
+**废弃**:`generator_v2`/`synthesizer`/`distractor_selector`/`gates`/`pipeline_v2` 主编排 + 对应 prompts/config。**拆解复用**:`adversarial_filter` 的孪生翻转构造 + 活求解器探针 + verdicts 续跑(去后置过滤外壳,提升为构造/核验主路径)。**借鉴语义**:`strategy_action_recognition` 的 6 skill 靶点 + flip_axis 标注。
+
+## 12. 非功能性需求(重写必须继承,防隐式丢失)
+
+| 维度 | 设计 |
+|------|------|
+| **持久化** | 逐 **unit** 原子成对落盘:pair 在内存 pending buffer 收齐 P+Q 后一次性进 accepted;全量 tmp + `os.replace`,**pair 同落或都不落**,崩溃不留半对 |
+| **幂等性** | 同 seed→同构造;孪生由 flip_axis 确定性构造;VLM 非确定为既有属性;`unit_hash` 检测续跑失效 |
+| **断点续跑** | progress(slot→status);pair 以"整对完成"为 accepted 单位;verdicts 表按 (question_id/hash/config 指纹) 三元组续跑;config 指纹失效作废脏续跑 |
+| **原子性** | 逐 unit 落盘;读回校验 pair 成对完整、剔孤儿;补构造续跑三件套(seq_offset/used_node_ids/embed_pool) |
+| **治理/遥测** | 所有 LLM/VLM 经 GovernedLLMClient 五层栈;session_id 透传每次调用必录 telemetry |
+| **并发/降级** | asyncio.Semaphore 限流;JSON 解析失败区分"契约违反 raise"vs"可降级 warning",不静默 |
+
+### 12.1 日志与可观测方案(须走 structured-logging skill)
+
+v3 产生大量运行时数据。设计阶段先给出**表结构骨架**(对齐 CLAUDE.md §3 Phase 1.4"设计阶段强制项",细节仍由 `structured-logging` skill 在 writing-plans 前定稿 + Wiki schema 注册):
+
+| 表 | 关键列 |
+|----|--------|
+| `unit_verdict` | unit_id, pair_id, sub_pattern, stage(层1-6), verdict(pass/fail), reason, metric_value, model, session_id |
+| `collapse_metrics` | pair_id, text_only_acc, single_frame_acc, placebo_drop, majority_vote_hit, slot_chi2, distractor_min_dist, multiformat_consistency |
+| `quarantine` | content_fingerprint, sub_pattern, quarantine_reason, round_no, ts |
+| `resume_state` | slot_id/unit_id, status, config_fingerprint(断点恢复+失效检测), seq_offset |
+| telemetry | 每次 LLM/VLM 调用(GovernedLLMClient 自动录,session_id 透传) |
+
+基线指标(各门通过率、backfill_yield_by_round、judge_disagreement_by_subpattern)定义与阈值进科研 YAML + run 快照。
+
+## 13. 错误处理
+
+孪生构造失败(找不到真实相反事实)→该 slot 重构或走产量降级;VLM/裁判异常→slot 级隔离宽异常重出不崩整批;kimi/NLI 裁判不可用→明确报错不静默跳过(防漏歧义);安慰剂/坍缩门模型不可用→报错。
+
+## 14. 测试策略
+
+- 单元:6 子模式孪生构造分流;bag-of-words 硬匹配校验;禁蕴含 NLI;逐维众数纯算法门;坍缩度量;pair 原子写/半写检测;双向 AND。
+- 集成:AR 端到端(mock 四家族)→ pair 成对落盘、**六层验证全部生效(含层6 MC vs Y/N 多格式交叉的 mock 与失败路径)**、坍缩验收。
+- 回归(pair 不拆):`stratified_sample`/`build_batches`/`run_inference`/`pools.build_pools` 四处 pair 同进同出、同批、配对聚合、孤儿剔除;gate_ladder 迁移续跑不崩。
+- 回归(非 AR byte-identical):11 非 AR generate-v2 字节级不变,含 rng 隔离验证。
+- Agent 类测试产出 MD(CLAUDE.md §4.6)。
+
+## 15. 分期实现(一个设计,writing-plans 分阶段)
+
+1. **契约地基**:QuestionUnit + ≥13 入口最小 pair 支持 + gate_ladder schema_version 迁移 + 非 AR rng 隔离 + pair 原子落盘。
+2. **帧感知事实抽取层(§9)+ 构造器 + fact_sampler**。**v3 必达 = 全部 6 子模式**(用户决策:接受帧感知抽取风险):
+ - **temporal** grounding=树结构保证(零模型);**其余 5**(cross_segment/premature/semantic_rigidity/fine_grained/evidence_gap)grounding=**§9 帧感知验证保证**(喂真实帧 + 双 VLM 交叉核 + Fact schema,见 §9);
+ - 六者共用 bag-of-words/禁蕴含/单轴/逐维众数平衡纯算法门 + 六层验证 + §9 题干模板对称 hard-fail(C8)。**帧感知抽取层是本阶段核心交付。**
+ - **运行时合法性门兜底**:fine_grained(需两个真实对比时刻,可能超 5 帧/段密度→重下密帧)、evidence_gap(幻觉风险最高)各挂运行时门(帧核实+坍缩+小样本人工抽检),门不过该 pair 走 fallback(fine_grained 退 finding B 路线、evidence_gap 退保守构造)——**兜底是质量安全网,不是把子模式踢出必达**。
+3. **六层验证栈**:坍缩度量(含 subtitle 输入)+ 看帧核实 + 帧错配安慰剂 + 难度探针(deepseek AgentLoop)+ kimi 独立歧义多裁判 + **多格式交叉一致(MC+Y/N)**。
+4. **对抗前移 + 产量**:失败指纹去重 + quarantine + 收敛上界 + 产量降级路径 + 补构造迭代。
+5. **验收面板 + 混格全链路**:反自证指标面板 + AR pair/非 AR single 端到端评分正确性验收。
+
+> 质量闸门前移:§7 纯算法指标(逐维众数/坍缩/槽位卡方)在阶段 2 即作 CI hard-fail;难度探针在阶段 3 出早期难度信号——不等阶段 5 才验质量(对治上一版盲飞根因)。
+
+## 16. 风险与回退
+
+| 风险 | 回退 |
+|------|------|
+| kimi 网关不可用 | 实现第一步验证;退回 qwen+MiniMax 异家族双看帧裁判并标注局限 |
+| **帧感知抽取风险(按 fact_type 的 failure taxonomy,C6)** | 逐类列失败路径 + 失败率进验收面板:稀疏帧漏检(顺序/变化/次数/速度类)→密帧重采/降级;双 VLM 共享盲区→错配/遮挡校准+人工 shared-error 抽样;**负事实不可证/双正解**→`negative_at_target` hard-fail(§9.1);事实过宽跨段复用→判别性筛;ABSTAIN 吞吐→SLA 终态(§6)|
+| **重复场景视频(同主体贯穿)大量事实非判别** | §9.1 判别性筛 + `negative_at_target` 剔双正解;产量由 §10 降级兜底(宁少勿歧义)。**spike 实证:连 GsYi 重复场景判别性仍 ~83%,双正解剔除率 ~17% 可控** |
+| **多主体繁忙场景的唯一绑定(Codex C4,spike 未压测)** | 样本多单主体,唯一绑定风险未验证 → writing-plans/实现早期补一次多主体段实测;不唯一时按 §9.1 拆分粒度处理 |
+| **fine_grained 产量/合法性**("两时刻不同方式"未必总能找到、未必构成合法单轴编辑) | 运行时合法性门(帧核实+坍缩+人工抽检);不过退 finding B 路线(生成候选+独立 wrongness 帧核验);产量降级兜底 |
+| **evidence_gap 虚构因果幻觉**(幻觉风险最高) | 运行时合法性门 + 保守构造 fallback;"不可确定"选项正/反题对称(防 C3 文本靶)|
+| **semantic_rigidity(必达静态类)** | 编辑轴=视觉真相 vs 字幕字面(非名词替换,避 VITATECS 静态退化);视觉真相由帧核实确证 |
+| fine_grained/静态类产量低(找不到真实对比时刻) | 产量降级路径;必要时该子模式先缓 |
+| 属性抽取层不准 | 抽取后 VLM 看帧复核;不准即弃该题 |
+| gate_ladder 迁移破坏冷启动算法保真 | schema_version + 迁移脚本 + 2:1/gamma-EMA/Beta 按 unit 重定义并写入保真清单 |
+| 混格题库拖累训练 | 阶段 5 先验评分正确性;必要时 AR pair 与非 AR 分池 |
+
+## 17. 核心算法保真
+
+v3 触及算法保真清单(`ARCHITECTURE.md §6`)第 5 项(信息阶梯冷启动 2:1/gamma-EMA/反泄漏)——因 gate_ladder 迁 unit_id,须逐条比对按 unit 重定义、不简化,并在 commit 标注。其余 11 项不涉及。
+
+> **gate-unit 迁移 ADR(C4,阶段 1 强制交付,不得占位进实现)**:以下每项须有**可测试**的明确定义——① unit 的冷启动"对/错"如何从 P/Q 得出(建议 unit 错=P 或 Q 任一错);② 2:1 错优先交错在 unit 上如何保持;③ unit `p_hat` 初值(Beta 先验参数);④ gamma-EMA 观测来源(predictions 折叠成 unit 观测再更新,防 `update_probs` 按 qid 匹配失效致 EMA 停摆);⑤ probe_quota 按 unit 还是按题抽;⑥ 存量 `gate_pools.json`(无 schema_version、qid 键)的处理:加 `schema_version` + 拒绝或一次性迁移脚本;⑦ 反泄漏(run_id 含 `_gate_` 过滤)不受影响的确认。
+
+## 相关
+- 范式依据:[2026-07-15-question-gen-paradigm-shift-construction-over-filtering.md]
+- 前序审核:[2026-07-15-question-gen-v2-adversarial-audit.md]
+- 被取代:[2026-07-15-question-gen-v2-grounded-contrastive-design.md]
+- 论文:`reference/papers-distractor-gen/`(6 篇深读)
diff --git a/research-wiki/findings/2026-07-15-question-gen-paradigm-shift-construction-over-filtering.md b/research-wiki/findings/2026-07-15-question-gen-paradigm-shift-construction-over-filtering.md
new file mode 100644
index 0000000..0637729
--- /dev/null
+++ b/research-wiki/findings/2026-07-15-question-gen-paradigm-shift-construction-over-filtering.md
@@ -0,0 +1,84 @@
+---
+id: question-gen-paradigm-shift-construction-over-filtering
+title: 出题范式转变 — 从"生成-打分-过滤"转向"构造优先 + 两正交独立信号"(六篇原文深读)
+type: finding
+created: 2026-07-15
+status: active
+supersedes-frame-of: 2026-07-15-question-gen-v2-grounded-contrastive-design.md
+---
+
+# 出题范式转变:构造优先,而非生成-过滤
+
+**触发**:v2 设计 + Codex 修法经两轮对抗审核后,判定撞上两堵"结构性墙"(2 VLM 凑不出独立 grounding 裁判;所有门合取减产不可行区)。用户指出这是"工程补丁/表面模仿",要求回原文挖核心可行机制。遂对 6 篇论文做原文深读(非二手摘要)。
+
+## 一、最关键的发现:两堵"墙"是错误框架的产物
+
+v2 设计(含 Phase A、Codex 修法)自始至终是**"自由生成候选池 → 用裁判打分 → 过滤挑好的"**。深读证明这是 **AFLite 亲口认栽的死路**:后置过滤的唯一信号(predictability)同时混入"真难题"和"歧义/烂题",一把尺量两样东西,拆不开——SNLI 过滤后**人类精度反降 10 分**(Table 3),证明筛出的"难题"塞满连人都做不对的歧义题。
+
+两堵墙只在这个框架里存在:
+- **墙 1(凑不出独立 grounding 裁判)** 只在"事后打分 grounding"时成立。构造范式里 grounding 由"锚定真实事实的最小编辑"保证,**根本不需要 grounding 裁判**。
+- **墙 2(合取门减产不可行区)** 只在"生成垃圾再硬过滤"时成立。构造正确 by design,yield 天然高,不需要一摞过滤门相乘。
+
+## 二、两种可行范式(按子模式可构造性分流)
+
+| 范式 | 代表论文 | grounding 由什么保证 | wrongness/唯一正解由什么保证 | 适用子模式 |
+|------|---------|---------------------|---------------------------|-----------|
+| **A. 受控构造** | Vinoground / TempCompass / VITATECS | 对真实时序/结构事实做**最小编辑** / **冲突孪生对**,锚定真实事实 → 结构保证 | 单轴取值改变 / 孪生体互斥 → **结构保证** | 可锚定到结构化事件时序属性者(顺序/方向/次数/主宾/时序性位置) |
+| **B. 生成-独立筛选** | GroundAttack | 独立模型对**真实帧的几何对齐度量**(把干扰项视觉对齐度顶到正解水平,抹掉 EOB) | **无保证——它的已知软肋**(§5 自认未验 wrongness);须外补独立核验 | 构造不了者(细粒度动作方式/纯感知) |
+
+**判定证据(构造 vs 过滤)**:
+- Vinoground:caption 侧受控构造(GPT-4 硬约束"exact same words, only permuted"),video 侧检索+人工核验。非生成-过滤。
+- TempCompass:纯受控构造(倒放/拼接/换序造冲突孪生),无生成后过滤,质量靠人工逐条校订。
+- VITATECS:受控生成 + precision-over-recall 三级严过滤兜底(NLI + 微调判别器"正反双序一致"+ 人工改标)。
+- GroundAttack:过量生成 128 + CLIP 视觉对齐筛 4。**唯一的生成-筛选范式**。
+
+## 三、三根之前完全没有的新支柱(原文证据)
+
+### 支柱 1:冲突孪生对 + 去捷径坍缩度量(TempCompass)
+"A 的干扰项 = B(孪生体)的正确答案"——静态帧完全相同、只翻转时序,**诱导 A 答对的捷径必让 B 答错**,单帧偏置+语言先验被结构性对冲归零。**有效性证明不是原始准确率,而是"加入冲突后掉回随机基线的幅度"(配对准确率/一致性)**——纯算法/结构验证,不需循环裁判。Table 5 铁证:Image LLM 41-49%→35-41%(逼近随机 30)。
+
+### 支柱 2:难度 vs 歧义 = 两个永不合并的正交独立信号(AdVQA)
+- 难度信号 = 活求解器答错(对抗前移出题当下,骗不过就重写,5-tries 循环)。
+- 质量/歧义信号 = **独立于求解器的多裁判答案一致性**(10 标注者,<6 confident 且无共识→剔)。
+- **核心不变量**:答案核验器**必须独立于被攻求解器**,用多裁判一致性证伪答案唯一性。把"作弊门"做成"求解器既当难度裁判又当答案裁判"= 退回 AFLite 单信号困境。
+- §4.5 直接证据:自动对抗无独立核验会制造答案漂移/歧义(Textfooler 错率 1.4% vs 人类闭环 38.1%)。
+
+### 支柱 3:grounding 靠构造锚定真实事实(Vinoground/VITATECS/GroundAttack 共识)
+"grounded"不来自"用了某模型打分",而来自:(A) 构造时锚定真实时序事实的最小编辑,或 (B) 独立模型对真实视觉证据的对齐度量。**表面模仿"加个打分器"会漏掉:打分对象必须是"与真实视觉 V 的对齐度",且打分器必须独立且真的看帧;且打分器不顺带保证 wrongness(GroundAttack 软肋)**。
+
+## 四、原文的关键警告(防止再次表面模仿)
+
+| 论文 | 警告 | 对我们的意义 |
+|------|------|------------|
+| VITATECS Table 8 | 随机改 1 个名词→74.5-82.1% 可解;改词数 1→3→90.3% 可解;"cautions against purely lexical methods" | 反事实必须沿**正交时序轴**做等信息量、非蕴含的**最小编辑**;名词/属性替换必退化成静态捷径可解的伪难题 |
+| VITATECS §3 | 纯静态感知(VISUAL/OCR/属性)**没有时序轴可翻转**,硬套单轴反事实会自败 | 不是所有 AR 子模式都适合构造范式;纯感知类需另想办法或剔除 |
+| GroundAttack §5 | selector 只优化 grounding+多样性,**wrongness 从未独立验证**;CLIP 选出的"视觉最贴合"候选恰最可能是第二正解 | 生成-筛选范式必须**外挂独立 wrongness 核验**(对齐 AdVQA) |
+| AFLite p8 | 过滤误删真易题 + 留下歧义题(人类精度降 10 分) | 后置作弊门只能兜底,不能当主力;质量必须构造/生成端保证 |
+| TempCompass Table 5 | 沿用原始准确率会自欺 | 度量必须用配对/一致性/去捷径坍缩,而非单题准确率 |
+
+## 五、对 6 个 AR 子模式的范式分流(初判,待 brainstorming 细化)
+
+| 子模式 | 范式 | 构造手段 | 备注 |
+|--------|------|---------|------|
+| temporal_reasoning_failure | **A 构造** | 树事件序列换序 → 冲突孪生对 + 双向 AND | 最佳适配(Vinoground/TempCompass Event-Order) |
+| cross_segment_entity_tracking | **A 构造** | 主宾互换 / first-last 翻转,保词袋 | Compositionality 最小编辑 |
+| fine_grained_visual_action | **B 生成-筛选** | qwen 过量生成 + MiniMax 看帧筛视觉对齐 + 独立 wrongness 核验 | 构造不了动作方式;2-VLM 独立性问题局限在此一个子模式 |
+| premature_evidence_anchoring | **A 构造(改造)** | 段位随机 + 跨段唯一性;本质是"时序性锚点"单轴 | 需确保锚定时序而非静态 |
+| evidence_gap_confabulation | **待定** | 二元(诚实 vs 虚构因果链),非静态感知 | 可能需独立设计;不套单轴反事实 |
+| semantic_rigidity | **待定/可能剔除** | 唯一同义改写 vs 字幕陷阱——偏静态语义 | VITATECS 警告静态类不适合;重新评估是否保留 |
+
+## 六、范式转变对 v2 设计的意义
+
+**现 v2 设计(2026-07-15-question-gen-v2-grounded-contrastive-design.md)的整个框架(生成-打分-过滤 + 事后 grounding 裁判 + 一摞合取门)被本 finding 取代。** 新框架:
+
+1. **构造优先**:能锚定到树的结构化事实的子模式,走"最小编辑 + 冲突孪生对",grounding/wrongness/唯一性由构造保证,绕开两堵墙。
+2. **生成-独立筛选仅用于构造不了的子模式**(fine_grained),且必须解耦 grounding(看帧对齐)与 wrongness(独立核验)两道关卡。
+3. **难度/歧义两正交独立信号**:难度=活求解器 Agent 答错(对抗前移,复用现有 AgentLoop);歧义=独立于求解器的多裁判/构造式答案唯一性核验。
+4. **度量革命**:验收用配对准确率/去捷径坍缩(text-only 盲答、单帧基线掉回随机),纯算法,非循环裁判。
+5. **LLM 角色收缩**:从"自由发明干扰项"变为"对结构化树事实做受控最小编辑的表面实现 + 结构化属性抽取"。
+
+## 相关
+- 论文原文:`reference/papers-distractor-gen/`(6 篇深读证据页码见各专读报告)
+- 被取代框架:[2026-07-15-question-gen-v2-grounded-contrastive-design.md]
+- 前序审核:[2026-07-15-question-gen-v2-adversarial-audit.md]
+- 前序诊断:[2026-07-15-question-gen-v2-diagnosis-and-strategy.md]
diff --git a/research-wiki/findings/2026-07-15-v3-frame-perception-spike-validation.md b/research-wiki/findings/2026-07-15-v3-frame-perception-spike-validation.md
new file mode 100644
index 0000000..f0c7d96
--- /dev/null
+++ b/research-wiki/findings/2026-07-15-v3-frame-perception-spike-validation.md
@@ -0,0 +1,51 @@
+---
+id: v3-frame-perception-spike-validation
+title: v3 §9 帧感知抽取机制 — 小样本实证验证结果
+type: finding
+created: 2026-07-15
+status: active
+---
+
+# v3 §9 帧感知抽取机制:小样本实证验证
+
+**目的**:进 writing-plans 前用真实数据验证 §9"帧感知 grounded 事实抽取 + negative_at_target + 双 VLM 交叉"能否成立(对治 Codex 三审 C3 密度 / C5 双正解,避免 v2 盲飞)。
+
+**方法**:5 个真实视频 × 3 个 L2 段,双 VLM(qwen3.6-plus + MiniMax-M3)对真实关键帧(5 帧/段)做结构化事实抽取 + 交叉核实 + 跨段 negative_at_target 检验,94 次 VLM 调用。脚本 `scratchpad/spike_frame_perception.py`(一次性)。**Claude 亲自 Read 帧图片当第三方裁判估 shared-error。**
+
+## 一、量化结果
+
+| 指标 | 结果 | 对照 §9.1 假设 |
+|------|------|--------------|
+| 可抽取率 | **15/15 = 100%** | 5 帧能感知结构化绑定 ✓ |
+| 判别性占比 | **~83%**(跨段 negative_at_target 持续=17%,5/30) | 判别性事实是可用主体 ✓ |
+| negative_at_target 有效性 | **有效**:被标"其它段也成立"的 yes **全是真持续事实**(魔方演示跨段真、舞台剧跨段真、performer 站台上跨段真),且 Claude 看帧确认 | C5 双正解可被抓 ✓ |
+| 双 VLM 一致率(holds 判定) | **26/30 = 87%** | 交叉核实高一致 |
+| 粗时序(顺序)可感知性 | **可以**:两 VLM 频繁从 5 帧感知有序变化("Frame0…Frame1…"/"transitions from…to…"),Claude 看帧确认"倒立→落地"序列真实 | C3 密度:粗时序 5 帧够 ✓ |
+| 细粒度方式(manner) | **确认是难点**:大量 `not_determinable` + 两 VLM 各说各话 | C3/C1:fine manner 5 帧不够 ⚠ |
+
+## 二、Claude 第三方看帧核查(shared-error)
+
+亲自 Read GsYi 3 帧核查:
+- seg0 frame0:确认男表演者桌上**倒立**、smartwater 舞台、旋转木马、观众——两 VLM 粗层感知**正确且一致,无幻觉**。
+- seg0 frame2:确认已**站地面**(桌空)——m3 的"倒立→落地"时序 claim **真实非幻觉**;qwen "manner 不可定"是保守。两者非"一致地错",是"保守 vs 详细"。
+- seg6 frame0:确认 performer **站平台、smartwater+旋转木马背景、女助手**——negative_at_target 判该事实在 seg6 成立**正确**,正是该剔的双正解。
+
+**结论:抽样中未见双 VLM 一致地幻觉;粗层事实、粗时序、negative_at_target 判定均锚在真实帧、经人工确认正确。**
+
+## 三、验证结论
+
+**§9 帧感知抽取机制在真实数据上站得住。** 两个 Codex Critical 的处置:
+- **C5 双正解 = 已缓解**:negative_at_target 实证有效(正确抓出跨段持续事实),剔除率 ~17% 可控,判别性主体 ~83% 可用。
+- **C3 密度 = 部分确认**:粗时序/顺序 5 帧可感知(利好 temporal/cross_segment/premature);**细粒度 manner 5 帧确实不够**(只影响 fine_grained,已有密帧/fallback 设计)。
+
+## 四、须折进设计的实证细化
+
+1. **fact_sampler 必须过滤到动作丰富段**:5 视频里 MYxL(幻灯片)/y2kg(录屏) 非动作视频——抽取虽成功但对 AR 无意义。fact_sampler 加"动作性"前置筛。
+2. **fine_grained 密帧从"可选兜底"升为"硬前置"**:manner 5 帧不够是实证事实 → fine_grained 必须密帧重采或走 B 路线,不是运行时才发现。
+3. **manner 层不能靠双 VLM 交叉核**(一致性低)→ manner 类 fact 走更严核实(更多帧+人工小样本),对齐 §9.1 C1 难度分层。
+4. **多主体绑定未被本 spike 压测**(样本多为单主体)——繁忙场景的唯一绑定(Codex C4)仍是开放风险,writing-plans 或实现早期补一次多主体段实测。
+5. 抽取器**倾向用详细但准确的 VLM**(m3 风格)作主抽、保守 VLM(qwen)作交叉。
+
+## 相关
+- 设计: [2026-07-15-question-gen-v3-construction-paradigm-design.md](§9/§9.1)
+- 脚本/原始数据: `scratchpad/spike_frame_perception.py`、`scratchpad/spike_results.json`、遥测 `logs/spike_vlm_telemetry.db`
diff --git a/research-wiki/graph/edges.json b/research-wiki/graph/edges.json
index 2b37d3e..9c3a9a0 100644
--- a/research-wiki/graph/edges.json
+++ b/research-wiki/graph/edges.json
@@ -190,6 +190,11 @@
"id": "plan:adversarial-question-gen-phaseB",
"label": "Adversarial Question-Gen Phase B",
"type": "plan"
+ },
+ {
+ "id": "plan:question-gen-v3-phase1-contract",
+ "label": "question-gen v3 Phase1 契约地基实现计划",
+ "type": "plan"
}
],
"links": [
@@ -360,6 +365,20 @@
"relation": "implements",
"evidence": "Phase B 实现计划",
"added": "2026-07-14T21:09:48.115521+00:00"
+ },
+ {
+ "source": "schema:v3-question-gen-logging",
+ "target": "design:question-gen-v3-construction-paradigm",
+ "relation": "implements",
+ "evidence": "v3 出题运行时数据 schema 实现设计 §12.1",
+ "added": "2026-07-15T09:18:41.713855+00:00"
+ },
+ {
+ "source": "plan:question-gen-v3-phase1-contract",
+ "target": "design:question-gen-v3-construction-paradigm",
+ "relation": "implements",
+ "evidence": "Phase1 契约地基",
+ "added": "2026-07-15T09:22:08.800078+00:00"
}
]
}
\ No newline at end of file
diff --git a/research-wiki/index.md b/research-wiki/index.md
index 3490291..f16f720 100644
--- a/research-wiki/index.md
+++ b/research-wiki/index.md
@@ -1,8 +1,8 @@
# Research Wiki 索引
-> 自动生成,更新时间:2026-07-14 21:09 UTC
+> 自动生成,更新时间:2026-07-15 09:22 UTC
-## design (29)
+## design (31)
- [2026-07-06-core-agent-adapters-llm-design](designs/2026-07-06-core-agent-adapters-llm-design.md) `design:2026-07-06-core-agent-adapters-llm-design`
- [2026-07-07-app-harness-design](designs/2026-07-07-app-harness-design.md) `design:2026-07-07-app-harness-design`
- [2026-07-07-core-evolution-design](designs/2026-07-07-core-evolution-design.md) `design:2026-07-07-core-evolution-design`
@@ -16,6 +16,8 @@
- [main.py 推理入口 + 初始 Prompt 集设计](designs/2026-07-09-main-inference-entry-design.md) `design:2026-07-09-main-inference-entry-design`
- [main.py 推理入口 + 初始 Prompt 集设计](designs/main-inference-entry.md) `design:main-inference-entry`
- [Per-Category Pool Strategy 设计](designs/per-category-pool-strategy.md) `design:per-category-pool-strategy`
+- [question-gen v2 — AR grounded-contrastive 重构(双模式生成 + 独立 VLM grounding + 生成端对抗)](designs/2026-07-15-question-gen-v2-grounded-contrastive-design.md) `design:2026-07-15-question-gen-v2-grounded-contrastive-design`
+- [question-gen v3 — 构造优先范式重构(全孪生 + 四家族真独立 + 坍缩度量)](designs/2026-07-15-question-gen-v3-construction-paradigm-design.md) `design:2026-07-15-question-gen-v3-construction-paradigm-design`
- [Spec-1 Agent 执行环境修复(解析容错+步级重试+摘要附实体)](designs/agent-runtime-fixes.md) `design:agent-runtime-fixes`
- [Spec-2 建树批量并行入口](designs/batch-tree-build.md) `design:batch-tree-build`
- [Spec-3 出题管线 v2(失败机理靶向+逐题质量门)](designs/question-gen-v2.md) `design:question-gen-v2`
@@ -33,16 +35,19 @@
- [赛题生成工具设计](designs/question-gen-synth.md) `design:question-gen-synth`
- [赛题生成工具设计(Question Generation Synthesis)](designs/2026-07-09-question-gen-synth-design.md) `design:2026-07-09-question-gen-synth-design`
-## finding (7)
+## finding (10)
- [2026-07-11-benchmark-failure-taxonomy](findings/2026-07-11-benchmark-failure-taxonomy.md) `finding:2026-07-11-benchmark-failure-taxonomy`
- [2026-07-11-question-gen-calibration-analysis](findings/2026-07-11-question-gen-calibration-analysis.md) `finding:2026-07-11-question-gen-calibration-analysis`
- [2026-07-14-ar30-too-easy-analysis](findings/2026-07-14-ar30-too-easy-analysis.md) `finding:2026-07-14-ar30-too-easy-analysis`
- [2026-07-14-hard-distractor-literature](findings/2026-07-14-hard-distractor-literature.md) `finding:2026-07-14-hard-distractor-literature`
- [2026-07-14-question-quality-gap-analysis](findings/2026-07-14-question-quality-gap-analysis.md) `finding:2026-07-14-question-quality-gap-analysis`
+- [2026-07-15-question-gen-v2-diagnosis-and-strategy](findings/2026-07-15-question-gen-v2-diagnosis-and-strategy.md) `finding:2026-07-15-question-gen-v2-diagnosis-and-strategy`
- [Harness 评估: Spec-1 修复验证 (infer_spec1check)](findings/eval-spec1check.md) `finding:eval-spec1check`
- [Harness 评估: Spec-2 批量并行建树](findings/eval-spec2-batch-tree-build.md) `finding:eval-spec2-batch-tree-build`
+- [v3 §9 帧感知抽取机制 — 小样本实证验证结果](findings/2026-07-15-v3-frame-perception-spike-validation.md) `finding:2026-07-15-v3-frame-perception-spike-validation`
+- [出题范式转变 — 从"生成-打分-过滤"转向"构造优先 + 两正交独立信号"(六篇原文深读)](findings/2026-07-15-question-gen-paradigm-shift-construction-over-filtering.md) `finding:2026-07-15-question-gen-paradigm-shift-construction-over-filtering`
-## plan (36)
+## plan (38)
- [2026-07-06-core-agent-adapters-llm](plans/2026-07-06-core-agent-adapters-llm.md) `plan:2026-07-06-core-agent-adapters-llm`
- [2026-07-07-app-harness](plans/2026-07-07-app-harness.md) `plan:2026-07-07-app-harness`
- [2026-07-07-core-evolution](plans/2026-07-07-core-evolution.md) `plan:2026-07-07-core-evolution`
@@ -60,6 +65,7 @@
- [2026-07-14-grounded-question-gen-phaseA-plan](plans/2026-07-14-grounded-question-gen-phaseA-plan.md) `plan:2026-07-14-grounded-question-gen-phaseA-plan`
- [2026-07-14-maintenance-pool](plans/2026-07-14-maintenance-pool.md) `plan:2026-07-14-maintenance-pool`
- [2026-07-14-task-type-strategy-framework](plans/2026-07-14-task-type-strategy-framework.md) `plan:2026-07-14-task-type-strategy-framework`
+- [2026-07-15-question-gen-v3-phase1-contract](plans/2026-07-15-question-gen-v3-phase1-contract.md) `plan:2026-07-15-question-gen-v3-phase1-contract`
- [Action Recognition 单题型首次训练实验计划](plans/action-recognition-training.md) `plan:action-recognition-training`
- [ActionRecognitionStrategy 特化实现计划 (Plan B)](plans/action-recognition-strategy.md) `plan:action-recognition-strategy`
- [Adversarial Question-Gen Phase B](plans/adversarial-question-gen-phaseB.md) `plan:adversarial-question-gen-phaseB`
@@ -70,6 +76,7 @@
- [main.py 推理入口 + 初始 Prompt 集实现计划](plans/main-inference-entry.md) `plan:main-inference-entry`
- [Maintenance Pool 自动补入实现计划](plans/maintenance-pool.md) `plan:maintenance-pool`
- [Per-Category Pool Strategy 实现计划](plans/per-category-pool-strategy.md) `plan:per-category-pool-strategy`
+- [question-gen v3 Phase1 契约地基实现计划](plans/question-gen-v3-phase1-contract.md) `plan:question-gen-v3-phase1-contract`
- [question_gen 模块实现计划](plans/question-gen.md) `plan:question-gen`
- [Spec-1 Agent 执行环境修复实现计划](plans/agent-runtime-fixes-plan.md) `plan:agent-runtime-fixes-plan`
- [Spec-2 建树批量并行实现计划](plans/batch-tree-build-plan.md) `plan:batch-tree-build-plan`
@@ -80,7 +87,11 @@
- [赛题生成工具实现计划](plans/question-gen-synth.md) `plan:question-gen-synth`
- [项目基础设施初始化计划](plans/infrastructure-setup.md) `plan:infrastructure-setup`
-## schema (3)
+## review (1)
+- [question-gen v2 设计对抗审核 — 六路独立核验(四层病灶闭合度 + 契约一致性)](reviews/2026-07-15-question-gen-v2-adversarial-audit.md) `review:2026-07-15-question-gen-v2-adversarial-audit`
+
+## schema (4)
+- [表结构 v3 出题日志/观测(unit_verdict / collapse_metrics / quarantine / facts / resume)](schemas/v3-question-gen-logging.md) `schema:v3-question-gen-logging`
- [表结构: adversarial_verdicts(Phase B agent 门判定)](schemas/adversarial-verdicts.md) `schema:adversarial-verdicts`
- [表结构: question_gen_items(逐题门判定)](schemas/question-gen-items.md) `schema:question-gen-items`
- [表结构: question_gen_runs(出题批次)](schemas/question-gen-runs.md) `schema:question-gen-runs`
diff --git a/research-wiki/log.md b/research-wiki/log.md
index 36d5b3e..6ffab24 100644
--- a/research-wiki/log.md
+++ b/research-wiki/log.md
@@ -92,3 +92,8 @@
- [2026-07-14 21:09 UTC] 新增 plan: Adversarial Question-Gen Phase B (plan:adversarial-question-gen-phaseB)
- [2026-07-14 21:09 UTC] 新增边: plan:adversarial-question-gen-phaseB --implements--> design:adversarial-question-gen-phaseB
- [2026-07-14 21:09 UTC] 重建索引: 79 篇页面
+- [2026-07-15 09:18 UTC] 新增边: schema:v3-question-gen-logging --implements--> design:question-gen-v3-construction-paradigm
+- [2026-07-15 09:18 UTC] 重建索引: 86 篇页面
+- [2026-07-15 09:22 UTC] 新增 plan: question-gen v3 Phase1 契约地基实现计划 (plan:question-gen-v3-phase1-contract)
+- [2026-07-15 09:22 UTC] 新增边: plan:question-gen-v3-phase1-contract --implements--> design:question-gen-v3-construction-paradigm
+- [2026-07-15 09:22 UTC] 重建索引: 88 篇页面
diff --git a/research-wiki/plans/2026-07-15-question-gen-v3-phase1-contract.md b/research-wiki/plans/2026-07-15-question-gen-v3-phase1-contract.md
new file mode 100644
index 0000000..a103cb4
--- /dev/null
+++ b/research-wiki/plans/2026-07-15-question-gen-v3-phase1-contract.md
@@ -0,0 +1,461 @@
+# question-gen v3 — Phase 1 契约地基 Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use subagent-driven-development to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** 建立 `QuestionUnit(single|pair)` 契约并让它贯穿 harness 采样/分批/聚合/续跑/gate 全部拆-pair 入口,使后续阶段产出的 AR pair 题能被评测/训练正确消费,且 11 非 AR single 题行为字节级不变。
+
+**Architecture:** 引入 `QuestionUnit` 判别联合(core 领域实体)+ `question_units.py` helper(build/flatten/validate/unit-correctness)。改造 pools/loader/batching/inference/runner/gate_ladder 使 pair **同池、同 batch 整锁、配对聚合(双向 AND)、同池切分**;correctness 分"逐题 predictions(溯源)/ unit correctness(进化消费)"两口径;pair 原子成对落盘;gate_ladder 加 schema_version 迁移;非 AR rng 独立 namespace。**在现有 `app/question_gen/` 内原地改,不建 _v3 目录。**
+
+**Tech Stack:** Python 3.11 / pytest / SQLite(run_store)/ asyncio。全程 `conda run -n Video-Tree-TRM`。
+
+**依据**:设计 `research-wiki/designs/2026-07-15-question-gen-v3-construction-paradigm-design.md` §5/§8/§12;日志 schema `research-wiki/schemas/v3-question-gen-logging.md`。本阶段**不产出题目**,只建契约,交付物 = 全套 pair-契约回归测试绿 + 非 AR byte-identical 绿。
+
+---
+
+## 文件结构映射
+
+| 文件 | 职责 | 改动 |
+|------|------|------|
+| `core/types.py` | 领域实体 | 加 `QuestionUnit` + `GeneratedQuestion` 4 字段 |
+| `app/harness/question_units.py` | unit helper(新建) | build/flatten/validate_units/unit_correctness |
+| `app/harness/pools.py` | 三池切分 | build_pools/_sample_excluding/_split_one_category 以 unit 为原子 + _q_to_dict/_dict_to_q 序列化 pair 字段 |
+| `app/question_gen/loader.py` | 采样/加载 | stratified_sample 按 unit + load_benchmark 读回 pair 字段 |
+| `app/harness/batching.py` | 分批 | build_batches unit 整锁 + _select_mixed_by_task_type 按 unit correctness 分桶 + 非 AR 独立 rng |
+| `app/harness/inference.py` | 聚合 | run_inference pair-level 双向 AND + unit 粒度 total/correct |
+| `app/harness/runner.py` | 训练主环 | correctness 消费点改 unit 视图 + checkpoint 存 unit_id 序列 |
+| `app/harness/gate_ladder.py` | 信息阶梯 | entry 迁 unit_id + schema_version 迁移 + BaselineCache unit 键 |
+| `core/evolution/validate.py` | e-process 统计 | pair_block/compute_accuracy 按 unit |
+| `app/harness/validate.py` | **gate 块实际执行**(Codex C-2)| baseline/candidate block、baseline_cache.get/put、n_used 按 unit |
+| `app/question_gen/run_store.py` | 落库(唯一 run_store,非 app/harness/)| 加 facts/unit_verdict/collapse_metrics/quarantine/resume_state 表 |
+| `app/question_gen/pair_atomic_writer.py` | pair 原子写 helper(新建)| pending buffer + os.replace + 孤儿剔除(wiring 挪 Phase 2)|
+
+---
+
+## Task 1: QuestionUnit 领域实体 + GeneratedQuestion 字段扩展
+
+**Files:**
+- Modify: `core/types.py`
+- Test: `tests/unit/test_question_unit.py`
+
+- [ ] **Step 1: 写失败测试**
+
+```python
+# tests/unit/test_question_unit.py
+from core.types import GeneratedQuestion, QuestionUnit
+
+def _q(qid, role="single", pair_id=None, flip_axis=None):
+ return GeneratedQuestion(
+ question_id=qid, video_id="v", task_type="Action Recognition",
+ question="?", options=("A. a", "B. b", "C. c", "D. d"), answer="A",
+ source_nodes=("n1",), difficulty="hard",
+ unit_id=pair_id or qid, pair_id=pair_id,
+ question_role=role, flip_axis=flip_axis,
+ )
+
+def test_single_defaults_backward_compatible():
+ q = GeneratedQuestion(question_id="q", video_id="v", task_type="X",
+ question="?", options=("A. a","B. b","C. c","D. d"), answer="A",
+ source_nodes=("n1",), difficulty="hard")
+ assert q.question_role == "single"
+ assert q.pair_id is None and q.flip_axis is None and q.unit_id == "q"
+
+def test_pair_unit_carries_two_questions():
+ p = _q("q_o", "pair_original", "pid", "before_after")
+ m = _q("q_m", "pair_mirror", "pid", "before_after")
+ u = QuestionUnit.from_pair(p, m)
+ assert u.kind == "pair" and u.size == 2 and u.unit_id == "pid"
+ assert {qq.question_id for qq in u.questions} == {"q_o", "q_m"}
+```
+
+- [ ] **Step 2: 跑测试确认失败** — Run: `conda run -n Video-Tree-TRM pytest tests/unit/test_question_unit.py -v` — Expected: FAIL(`QuestionUnit` 未定义 / 字段缺失)
+
+- [ ] **Step 3: 实现**
+
+```python
+# core/types.py —— GeneratedQuestion 增字段(默认值保非 AR 不变)
+# 在 GeneratedQuestion dataclass 追加:
+ unit_id: str = "" # 默认在 __post_init__ 回填为 question_id
+ pair_id: str | None = None
+ question_role: str = "single" # single | pair_original | pair_mirror
+ flip_axis: str | None = None
+# __post_init__ 中:if not self.unit_id: object.__setattr__(self, "unit_id", self.pair_id or self.question_id)
+
+@dataclass(frozen=True)
+class QuestionUnit:
+ kind: str # "single" | "pair"
+ unit_id: str
+ task_type: str
+ questions: tuple[GeneratedQuestion, ...]
+ unit_hash: str = "" # P/Q payload 合成 hash,断点续跑失效检测(T11 用)
+ # 注:设计 §8 的 collapse_metric 是验收产物,不进 Phase 1 契约实体,Phase 3/5 落 collapse_metrics 表
+ @property
+ def size(self) -> int: return len(self.questions)
+ @classmethod
+ def from_single(cls, q):
+ return cls("single", q.unit_id, q.task_type, (q,))
+ @classmethod
+ def from_pair(cls, original, mirror):
+ assert original.pair_id and original.pair_id == mirror.pair_id
+ assert original.video_id == mirror.video_id and original.task_type == mirror.task_type
+ assert original.flip_axis == mirror.flip_axis
+ return cls("pair", original.pair_id, original.task_type, (original, mirror))
+```
+
+- [ ] **Step 4: 跑测试确认通过** — Run: 同上 — Expected: PASS
+- [ ] **Step 5: 提交** — `git add core/types.py tests/unit/test_question_unit.py && git commit`(用 commit skill)
+
+---
+
+## Task 2: question_units.py helper(组装/展开/校验/unit correctness)
+
+**Files:**
+- Create: `app/harness/question_units.py`
+- Test: `tests/unit/test_question_units_helper.py`
+
+- [ ] **Step 1: 写失败测试**
+
+```python
+# tests/unit/test_question_units_helper.py
+from app.harness.question_units import build_units, flatten_units, validate_units, unit_correctness
+from core.types import GeneratedQuestion
+
+def _q(qid, role="single", pid=None):
+ return GeneratedQuestion(question_id=qid, video_id="v", task_type="AR",
+ question="?", options=("A. a","B. b","C. c","D. d"), answer="A",
+ source_nodes=("n",), difficulty="hard", pair_id=pid, question_role=role,
+ unit_id=pid or qid, flip_axis="ax" if pid else None)
+
+def test_build_units_groups_pair_and_keeps_single():
+ qs = [_q("s1"), _q("po","pair_original","p"), _q("pm","pair_mirror","p")]
+ units = build_units(qs)
+ kinds = sorted(u.kind for u in units)
+ assert kinds == ["pair", "single"]
+
+def test_validate_units_rejects_orphan_pair():
+ import pytest
+ with pytest.raises(ValueError):
+ validate_units(build_units([_q("po","pair_original","p")])) # 只 1 条
+
+def test_flatten_roundtrip():
+ qs = [_q("po","pair_original","p"), _q("pm","pair_mirror","p")]
+ assert {q.question_id for q in flatten_units(build_units(qs))} == {"po","pm"}
+
+def test_unit_correctness_bidirectional_and():
+ qs = [_q("po","pair_original","p"), _q("pm","pair_mirror","p")]
+ u = build_units(qs)[0]
+ assert unit_correctness(u, {"po": True, "pm": True}) is True
+ assert unit_correctness(u, {"po": True, "pm": False}) is False # AND
+```
+
+- [ ] **Step 2: 跑确认失败** — `conda run -n Video-Tree-TRM pytest tests/unit/test_question_units_helper.py -v` — Expected: FAIL
+
+- [ ] **Step 3: 实现**
+
+```python
+# app/harness/question_units.py
+"""QuestionUnit 组装/展开/校验/单元正确性——pair 契约唯一入口。"""
+from collections import defaultdict
+from core.types import GeneratedQuestion, QuestionUnit
+
+def build_units(questions: list[GeneratedQuestion]) -> list[QuestionUnit]:
+ by_pair: dict[str, list[GeneratedQuestion]] = defaultdict(list)
+ singles: list[QuestionUnit] = []
+ for q in questions:
+ if q.pair_id:
+ by_pair[q.pair_id].append(q)
+ else:
+ singles.append(QuestionUnit.from_single(q))
+ pairs = []
+ for pid, qs in by_pair.items():
+ if len(qs) != 2:
+ raise ValueError(f"pair {pid} 数量={len(qs)}≠2(孤儿)")
+ o = next(q for q in qs if q.question_role == "pair_original")
+ m = next(q for q in qs if q.question_role == "pair_mirror")
+ pairs.append(QuestionUnit.from_pair(o, m))
+ return singles + pairs
+
+def flatten_units(units: list[QuestionUnit]) -> list[GeneratedQuestion]:
+ return [q for u in units for q in u.questions]
+
+def validate_units(units: list[QuestionUnit]) -> list[QuestionUnit]:
+ for u in units:
+ if u.kind == "pair" and u.size != 2:
+ raise ValueError(f"unit {u.unit_id} pair 不成对")
+ return units
+
+def unit_correctness(unit: QuestionUnit, per_q: dict[str, bool]) -> bool:
+ """AR pair = P AND Q;single = 单题。缺任一条 → KeyError(防静默)。"""
+ return all(per_q[q.question_id] for q in unit.questions)
+```
+
+- [ ] **Step 4: 跑确认通过** — Expected: PASS
+- [ ] **Step 5: 提交**
+
+---
+
+## Task 3: pools.py 三池切分以 unit 为原子(pair 同池)
+
+**Files:**
+- Modify: `app/harness/pools.py`(`build_pools` / `_sample_excluding` / `_split_one_category`)
+- Test: `tests/unit/test_pools_pair_atomic.py`
+
+- [ ] **Step 1: 写失败测试**(pair 两题必同池,不被 progressive exclusion 劈开)
+
+```python
+# tests/unit/test_pools_pair_atomic.py
+from app.harness.pools import build_pools
+from core.types import GeneratedQuestion
+
+def _pair(pid):
+ base = dict(video_id="v", task_type="AR", question="?",
+ options=("A. a","B. b","C. c","D. d"), answer="A",
+ source_nodes=("n",), difficulty="hard", pair_id=pid, unit_id=pid, flip_axis="ax")
+ return [GeneratedQuestion(question_id=f"{pid}_o", question_role="pair_original", **base),
+ GeneratedQuestion(question_id=f"{pid}_m", question_role="pair_mirror", **base)]
+
+def test_pair_never_split_across_pools():
+ # 真实签名(app/harness/pools.py:51):build_pools(questions, correctness, diag_cfg, val_cfg, test_cfg, baseline_run_id)
+ qs = [q for pid in [f"p{i}" for i in range(12)] for q in _pair(pid)]
+ cfg = {"ratio": 0.34} # 用真实 PoolConfig/口径填三档;此处示意
+ pools = build_pools(qs, correctness={}, diag_cfg=cfg, val_cfg=cfg, test_cfg=cfg, baseline_run_id="b")
+ loc = {}
+ for name, pool in pools.items():
+ for q in pool:
+ loc.setdefault(q.pair_id, set()).add(name)
+ assert all(len(s) == 1 for s in loc.values()), "pair 被劈到多个池"
+```
+
+> **实现者注**:先 Read `app/harness/pools.py:51` 确认 `diag_cfg/val_cfg/test_cfg` 的真实类型(PoolConfig dataclass 还是 dict),测试用真实构造。**红因必须是"pair 被拆"而非 TypeError**——签名不对会假红。
+
+- [ ] **Step 2: 跑确认失败**(现按 question_id 互斥会劈开)— Expected: FAIL(断言 pair 被拆,非 TypeError)
+
+- [ ] **Step 3: 实现** — `build_pools` 内先 `units = build_units(questions)`;三次 `_sample_excluding` 的互斥集合与采样对象都改 **unit_id**;`_split_one_category` 输入改 unit;**`build_incremental`(`pools.py:822-860`)的 categories qid 返回也改 unit 口径**;最后 `flatten_units` 展开。exclude 集合用 `unit.unit_id`。保留原 test→val→diag 顺序与比例(按 unit 计数)。
+
+- [ ] **Step 4: 跑确认通过** — Expected: PASS
+- [ ] **Step 5: 提交**
+
+---
+
+## Task 4: loader.stratified_sample 按 unit + load_benchmark 读回 pair 字段
+
+**Files:**
+- Modify: `app/question_gen/loader.py`(`stratified_sample` / `load_benchmark`)
+- Test: `tests/unit/test_loader_unit_sampling.py`
+
+- [ ] **Step 1: 写失败测试**:(a) pair 采样同进同出不拆;(b) 比例按 unit 计数;(c) **min_per_class 补足路径 `_backfill_per_class`(loader.py:154-172)不拆 pair**;(d) load_benchmark 读回 `pair_id`/`question_role`/`flip_axis`(`.get` 兼容旧 JSON)。
+- [ ] **Step 2: 跑确认失败**
+- [ ] **Step 3: 实现** — `stratified_sample` 先 `build_units`,按 unit 分层/去重/补足/`rng.sample`(用 Task 5 的 `_rng_ns`)、`_backfill_per_class` candidates 按 unit 枚举,返回前 `flatten_units`;`load_benchmark` 反序列化补 `pair_id=d.get("pair_id")` 等 4 字段。
+- [ ] **Step 4: 跑确认通过**
+- [ ] **Step 5: 提交**
+
+---
+
+## Task 5: batching unit 整锁 + 按 unit correctness 分桶 + 非 AR 独立 rng
+
+**Files:**
+- Modify: `app/harness/batching.py`(`build_batches` / `_select_mixed_by_task_type` / `_distribute_large_classes`)
+- Test: `tests/unit/test_batching_pair_lock.py`
+
+- [ ] **Step 1: 写失败测试**:(a) 同 pair_id 两题落**同一 batch**(像小类整组不拆);(b) pair 按 **unit correctness(双向 AND)** 落 correct/error 桶,不因 P 对 Q 错被劈;(c) **非 AR byte-identical**:AR pair 折叠不改变非 AR 的 `rng.sample`/`shuffle` 抽样序列(黄金对照)。
+- [ ] **Step 2: 跑确认失败**
+- [ ] **Step 3: 实现** — 分批输入改 unit 列表;FFD 容量按 `unit.size`;`_select_mixed_by_task_type` 用 `unit_correctness` 分桶、以 unit 为分发粒度;**AR 与非 AR 各用独立稳定派生的 rng**(**禁用 Python 内置 `hash()`——受 hash randomization 影响跨进程不可复现**):
+
+```python
+import hashlib
+def _rng_ns(seed: int, ns: str):
+ import random
+ h = int.from_bytes(hashlib.sha256(f"{ns}:{seed}".encode()).digest()[:8], "big")
+ return random.Random(h)
+# 非 AR 用 _rng_ns(seed, "nonAR"),AR pair 用 _rng_ns(seed, "AR"),二者 draw 流互不干扰
+```
+
+把 namespace 派生集中到此 helper(loader/pools/batching 共用),使 unit 折叠不干扰非 AR draw 流。flatten 前保证 pair 同 batch。
+- [ ] **Step 4: 跑确认通过**
+- [ ] **Step 5: 提交**
+
+---
+
+## Task 6: inference pair-level 双向 AND 聚合
+
+**Files:**
+- Modify: `app/harness/inference.py`(`run_inference` 聚合段)
+- Test: `tests/unit/test_inference_pair_aggregate.py`
+
+- [ ] **Step 1: 写失败测试**:逐题推理照常写 predictions;pair 按 pair_id 收齐两条合成 1 条 unit record,`pair 正确 = P对 AND Q对`;`InferenceResult.total/correct/per_task_type` 按 **unit 粒度**(total=single 数+pair 数);孤儿 pair(收不齐)→ 告警并剔除不计入 total(不静默)。
+- [ ] **Step 2: 跑确认失败**
+- [ ] **Step 3: 实现** — 聚合前 `build_units`;per-question prediction 仍逐题落 predictions 表;unit 层用 `unit_correctness` 计 correct;孤儿按设计 §8(聚合入口)+ §12(原子性/读回校验)剔除+告警。
+- [ ] **Step 4: 跑确认通过**
+- [ ] **Step 5: 提交**
+
+---
+
+## Task 7: correctness 三对象 API + runner 消费点改 unit 视图
+
+**Files:**
+- Modify: `app/harness/runner.py`(rollout 回写 163-190 / accept 合并 1117-1141 / val 回写 1442-1454 / quadrant / momentum 1561 / probation 1385-1428)、`core/evolution/validate.py`(`pair_block:12` / `compute_accuracy:72`)、**`app/harness/validate.py`(C-2 关键遗漏:gate 块实际执行路径)**
+- Test: `tests/unit/test_correctness_unit_view.py`、`tests/unit/test_gate_block_unit.py`
+
+- [ ] **Step 1: 写失败测试**:(a) 进化引擎(gate/quadrant/momentum/probation/pair_block/compute_accuracy)消费 **unit correctness**(AR=双向 AND、非 AR=单题);逐题 predictions 仅溯源;混格下 e-process delta / 准确率分母按 unit 计、不被 P/Q 单题计分污染。(b) **`app/harness/validate.py::validate_skill_local` 的 gate 块按 unit 跑**——`baseline_cache.get/put` 键含 unit_id(`validate.py:282-301`)、块 qids 换 unit 折叠(`:553-565`)、`n_used` 按 unit 累加(非 `len(chunk)` 逐题)。
+- [ ] **Step 2: 跑确认失败**
+- [ ] **Step 3: 实现** — 新增 `unit_correctness_view(units, per_q) -> dict[unit_id,bool]`;上述 6+ runner 调用点从 `correctness[qid]` 改消费 unit view;`core/evolution/validate.py::pair_block` 按 unit 折叠比对基线/候选臂、`compute_accuracy` 分母改 unit 数;**`app/harness/validate.py` 的 baseline/candidate block、evidence rows、`n_used`、`baseline_cache.get/put` 全部改 unit 口径,predictions 仍逐题溯源**(否则 gate 保真定义不进实际运行路径);quadrant/momentum 键改 unit_id。
+- [ ] **Step 4: 跑确认通过**
+- [ ] **Step 5: 提交**(commit message 标注核心算法保真#5 相关)
+
+---
+
+## Task 8: gate_ladder 迁 unit_id + schema_version 迁移
+
+**Files:**
+- Modify: `app/harness/gate_ladder.py`(`LadderEntry:43` / `build_cold_entries:57` / `ladder_for:135` / `update_probs:167` / `GatePools.save/load:181/200` / `BaselineCache:277`)
+- 依赖: `app/harness/validate.py` 的 `baseline_cache.get/put(..., q.question_id)` 调用侧(T7 已改 unit 键;本 Task 保证 gate_ladder 侧 key schema 与之对齐)
+- Test: `tests/unit/test_gate_ladder_unit_migration.py`
+
+- [ ] **Step 1: 写失败测试**:(a) `LadderEntry` 按 unit_id;(b) 冷启动"错优先 2:1"unit 错=P 或 Q 任一错;(c) `update_probs` 观测先折叠成 unit 再匹配(防按 qid 匹配失效致 EMA 停摆);(d) `GatePools.save/load` 带 `schema_version`;存量无版本 json 加载→明确报错或走迁移(不静默混用);(e) `BaselineCache` 键含 unit_id。
+- [ ] **Step 2: 跑确认失败**
+- [ ] **Step 3: 实现** — 见设计 §17 gate-unit 迁移 ADR 七项;`p_hat` 初值 Beta 先验按 unit 定义;probe_quota 按 unit 抽;反泄漏(run_id 含 `_gate_` 过滤)不变。写一次性迁移函数 `migrate_gate_pools_v1_to_unit()`。
+- [ ] **Step 4: 跑确认通过**
+- [ ] **Step 5: 提交**(标注核心算法保真#5,需逐行比对参考 `Video-Tree-TRM4/core/harness/gate_ladder.py`)
+
+---
+
+## Task 9: pools.json 冻结/解冻序列化 pair 字段
+
+**Files:**
+- Modify: `app/harness/pools.py`(`_q_to_dict` / `_dict_to_q` / `save_pools.categories`)
+- Test: `tests/unit/test_pools_serialization.py`
+
+- [ ] **Step 1: 写失败测试**:pair 冻结进 pools.json 再 `load_pools`,`pair_id`/`question_role`/`flip_axis`/`unit_id` 不丢;`categories` 块以 unit 记 train/val;旧 JSON(无字段)`.get` 兼容不崩。
+- [ ] **Step 2: 跑确认失败**
+- [ ] **Step 3: 实现** — `_q_to_dict` 写出 4 字段;`_dict_to_q` `.get(..., 默认)` 读回并回填 unit_id。
+- [ ] **Step 4: 跑确认通过**
+- [ ] **Step 5: 提交**
+
+---
+
+## Task 10: checkpoint/resume + 非 AR byte-identical 黄金测试
+
+**Files:**
+- Modify: `app/harness/runner.py`(`epoch_batches` / `_batch_from_ids`)
+- Test: `tests/integration/test_checkpoint_pair.py`、`tests/unit/test_non_ar_byte_identical.py`
+
+- [ ] **Step 1: 写失败测试**:(a) checkpoint 存 **unit_id 序列**,`_batch_from_ids` 恢复时展开完整 unit(断点续跑后 pair 不拆);(b) **纯非 AR 题库**过 pools/batching/inference 的抽样与分批结果与"引入 QuestionUnit 前"**逐字节一致**(黄金文件);(c) **`runner.py:1561` momentum 采样**(`random.Random(epoch).sample(candidates)`,Codex I-3):混格下 AR pair 折叠会改 candidates 长度/顺序→非 AR momentum 样本漂移。**要么** momentum candidates 用独立 rng namespace(Task 5 helper)+ 加混格 momentum golden,**要么**在计划显式记录"Phase 1 不保证 momentum 混格 byte-identical"作为设计偏差。二选一,不留隐患。
+- [ ] **Step 2: 跑确认失败**
+- [ ] **Step 3: 实现** — checkpoint 序列化 unit_id;恢复用 `build_units` + 展开;非 AR 走 size=1 unit 且独立 rng namespace(Task 5)保证黄金一致。
+- [ ] **Step 4: 跑确认通过**
+- [ ] **Step 5: 提交**
+
+---
+
+## Task 11: pair 原子成对落盘 helper(可复用纯件;wiring 挪 Phase 2)
+
+**背景(Codex C-3)**:`app/question_gen/run_store.py` 是 SQLite 日志类,**无 on_accept/accepted-JSON 钩子**;真实"accepted 题库文件写入"在生成侧(旧 `pipeline_v2` 的 on_accept 回调 / `adversarial_filter.write_final_bank`,均 Phase 2 重建)。故 Phase 1 **只建可复用、可独立测试的 pair 原子写 helper**,实际接到新 `pipeline.on_accept` 的 wiring **在 Phase 2 做**(新 pipeline 落地时)。
+
+**Files:**
+- Create: `app/question_gen/pair_atomic_writer.py`(纯 helper)
+- Test: `tests/unit/test_pair_atomic_write.py`
+
+- [ ] **Step 1: 审计定位真实 accepted 写入点** — `grep -rn "on_accept\|write_final_bank\|accepted_questions" app/question_gen/ tools/` 记录当前 accepted 文件写入函数(供 Phase 2 wiring 参考),写入计划注释。
+
+- [ ] **Step 2: 写失败测试** — 针对 `pair_atomic_writer`:pending buffer 按 pair_id 收齐 original+mirror 才一次性 emit unit;`write_accepted(path, units)` 全量 tmp + `os.replace`;模拟"只落 P 未落 Q"→读回 `validate_units` 剔孤儿;single 恒直接成 unit;`unit_hash` 不一致→拒。
+
+- [ ] **Step 3: 跑确认失败**
+
+- [ ] **Step 4: 实现** — `PairPendingBuffer.add(q)`(按 pair_id 收齐才 emit)+ `write_accepted(path, units)`(tmp+os.replace)+ `read_accepted(path)`(`validate_units` 剔孤儿)。纯函数,不依赖 pipeline。
+
+- [ ] **Step 5: 跑确认通过 + 提交**(计划注释标注:on_accept wiring 见 Phase 2)
+
+---
+
+## Task 12: run_store v3 表(facts/unit_verdict/collapse_metrics/quarantine/resume_state)
+
+**Files:**
+- Modify: `app/question_gen/run_store.py`(建表 + insert 方法)
+- Test: `tests/unit/test_run_store_v3_tables.py`
+
+- [ ] **Step 1: 写失败测试**:`store.insert_fact/insert_unit_verdict/insert_collapse_metrics/quarantine/upsert_resume_state` 落库+读回;quarantine 内容指纹去重(同指纹 upsert 不重复);ts 由外部传入(禁进程内 now,保幂等/可复现)。
+- [ ] **Step 2: 跑确认失败**
+- [ ] **Step 3: 实现** — 按 `research-wiki/schemas/v3-question-gen-logging.md` 5 张表建表 + 索引 + insert 方法;对齐 CLAUDE.md §4.8。**ts 边界(Codex I-4)明确**:v3 新表(facts/unit_verdict/collapse_metrics/quarantine/resume_state)**ts 一律外部传入、禁进程内 now**(保幂等可复现);v2 deprecated 表(question_gen_runs/items/adversarial_verdicts)**暂保留现有 now() 行为不动**(Phase 2/4 删)——不因本 Task 误改旧表。
+- [ ] **Step 4: 跑确认通过**
+- [ ] **Step 5: 提交**
+
+---
+
+## Task 13: Phase 1 集成回归(pair 契约全链路 + 混格评分)
+
+**Files:**
+- Test: `tests/integration/test_v3_contract_e2e.py`
+
+- [ ] **Step 1: 写集成测试**:构造混格题库(若干 AR pair + 若干非 AR single),跑 build_pools→build_batches→run_inference 全链路,断言:pair 全程不拆、同批、双向 AND 聚合正确、unit 粒度 total/correct 正确、孤儿被剔、非 AR byte-identical。
+- [ ] **Step 2: 跑确认失败**
+- [ ] **Step 3: 补齐**前序 Task 遗漏
+- [ ] **Step 4: 跑全套** — `conda run -n Video-Tree-TRM pytest tests/ -q` 全绿 + 覆盖率≥80%
+- [ ] **Step 5: 提交**
+
+---
+
+## Task 14: 旧代码处理与死代码清除(Phase 1 收尾)
+
+**Files:**
+- Modify: `app/harness/pools.py` / `batching.py` / `inference.py` / `runner.py` / `gate_ladder.py` / `core/evolution/validate.py`(Task 3-10 改过的文件)、`app/question_gen/run_store.py`
+- Test: `tests/unit/test_no_dead_perquestion_paths.py`
+
+**背景**:Task 3-10 按 CLAUDE.md §4.2"直接改原文件"做**原地替换**(非新增并行路径),本 Task 确保替换后不留孤儿死代码,并明确 v2 落库表的过渡去向。**不删 v2 生成模块**(那在 Phase 2,替代品落地后才删)。
+
+- [ ] **Step 1: 审计 unit 迁移后的孤儿函数** — 对 Task 3-10 改过的文件,用 `conda run -n Video-Tree-TRM ruff check --select F811,F401 app/harness/ core/evolution/` + `grep -rn "def _batch_from_ids\|def <被替换的逐题helper>" app/harness/` 逐一确认:被 unit 版替换掉的旧逐题函数/helper(如仅旧 `_batch_from_ids` 逐题重建、旧逐题 correctness helper)是否仍被引用。列出无引用者。
+
+- [ ] **Step 2: 写守卫测试**(防旧逐题路径复活/残留)
+
+```python
+# tests/unit/test_no_dead_perquestion_paths.py
+import inspect
+from app.harness import batching, pools, inference
+
+def test_no_parallel_perquestion_split_helpers():
+ """契约迁 unit 后,不得残留会拆 pair 的旧逐题分批/切分/gate 块路径。"""
+ from app.harness import validate as hvalidate # gate 块真实路径
+ src = inspect.getsource(batching) + inspect.getsource(pools) + inspect.getsource(hvalidate)
+ # 旧逐题标志(按实际被替换的函数名调整):断言已被 unit 版取代、无并行残留
+ assert "correctness.get(qid)" not in src, "batching 仍有逐题分桶残留"
+ # gate 块残留探测(Codex:validate.py 是逐题 gate 核心残留点)
+ assert "baseline_cache.get(" not in src or "unit_id" in src, "validate baseline_cache 仍按 qid"
+ assert "n_used += len(chunk)" not in src, "validate n_used 仍逐题累加(应按 unit)"
+ assert src.count("build_units") >= 1 or "unit_id" in src, "pools/batching/validate 未走 unit 化"
+```
+
+- [ ] **Step 3: 删除孤儿死代码 + 标注 v2 表过渡** — 删掉 Step 1 确认无引用的旧逐题函数;`run_store.py` 里 v2 表(`question_gen_runs`/`question_gen_items`/`adversarial_verdicts`)**保留但加 deprecation 注释**(`# DEPRECATED(v3): v2 生成落库,Phase 2 生成替换后删;过渡期与 v3 表并存`)——不在 Phase 1 删(生成逻辑还没换)。
+
+- [ ] **Step 4: 跑测试 + lint** — Run: `conda run -n Video-Tree-TRM pytest tests/unit/test_no_dead_perquestion_paths.py -v && ruff check app/harness/ core/evolution/` — Expected: PASS + 无 F811/F401
+
+- [ ] **Step 5: 提交**
+
+---
+
+## Self-Review 检查
+
+- **Spec 覆盖**:设计 §8 的 ≥13 入口逐一对应——pools 三池(T3)/序列化(T9)、loader(T4)、batching 整锁+分桶+rng(T5,T10)、inference 聚合(T6)、correctness+validate(T7)、gate_ladder+迁移(T8)、checkpoint+BaselineCache(T10,T8)、load_benchmark(T4)、pair 原子写(T11)、run_store 表(T12)。§12 非功能性(原子/续跑/幂等)→ T10/T11/T12。日志 schema → T12。**全覆盖。**
+- **类型一致**:`QuestionUnit`/`build_units`/`unit_correctness`/`flatten_units`/`validate_units` 在 T1/T2 定义,T3-T13 一致引用。
+- **无占位**:各 Task 有真实测试+实现骨架+命令。机械 Task(T4/T9)复用 T2/T3 已给模式。
+
+## 核心算法保真校验
+
+本阶段触及**核心算法#5 信息阶梯**(gate_ladder 迁 unit,Task 7/8):须逐行比对参考 `/home/iomgaa/Projects/Video-Tree-TRM4/core/harness/gate_ladder.py`(冷启动 2:1/gamma-EMA/Beta 先验/反泄漏),按 unit 重定义**不简化**,Task 8 已设"保真校验"检查点。其余 12 项不涉及。
+
+## 后续阶段(各自成计划)+ 旧代码删除清单(显式,防隐式漂)
+
+Phase 2 帧感知抽取+构造器 / Phase 3 六层验证栈 / Phase 4 对抗前移+产量 / Phase 5 验收面板+混格全链路——进入时各写独立 plan。
+
+**旧代码删除必须落成对应阶段的显式任务(不得只"替换"而留死壳):**
+
+| 废弃模块 | 处置 | 落哪阶段(显式删除任务)|
+|---------|------|----------------------|
+| `pipeline_v2.py` | 被 `pipeline`(帧感知主编排)原地替换 | **Phase 2** |
+| `generator_v2.py` | 被 `constructor`+`twin_builder`(构造)取代 | **Phase 2** |
+| `synthesizer.py` | v1 遗留生成,直接删 | **Phase 2** |
+| `distractor_selector.py` | 打分 selector 废(GroundAttack 软肋),删 | **Phase 2** |
+| `gates.py` | 四门(AFLite 单信号死路)废;`blind_answer` 思路迁 §7 坍缩度量后删 | **Phase 3**(坍缩度量落地后)|
+| `adversarial_filter.py` | 拆解:孪生构造→Phase 2、活求解器探针/verdicts 续跑→Phase 4;机制迁完删壳 | **Phase 4** |
+| `store/prompts/question_gen/ar_distractor_*`、`gate_*` | 打分/后置门 prompt 废;`ar_mirror_question`/`gate_blind_answer` 借鉴重写后删旧 | Phase 2/3 |
+| `config/question_gen_ar30.yaml` 的 `candidate_pool_size`/`selector_delta_*` | selector 参数废 | Phase 2 |
+| v2 run_store 表 `question_gen_runs/items/adversarial_verdicts` | Phase 1 加 deprecation 注释保留;生成替换后删 | Phase 2(items/runs)/ Phase 4(verdicts)|
+
+> 每阶段计划的最后一个 Task **必须**是"旧代码删除 + 死代码清除 + lint 无 F811/F401",与本 Phase 的 Task 14 同构。
diff --git a/research-wiki/reviews/2026-07-15-question-gen-v2-adversarial-audit.md b/research-wiki/reviews/2026-07-15-question-gen-v2-adversarial-audit.md
new file mode 100644
index 0000000..88e7474
--- /dev/null
+++ b/research-wiki/reviews/2026-07-15-question-gen-v2-adversarial-audit.md
@@ -0,0 +1,104 @@
+---
+id: question-gen-v2-adversarial-audit
+title: question-gen v2 设计对抗审核 — 六路独立核验(四层病灶闭合度 + 契约一致性)
+type: review
+created: 2026-07-15
+status: blocking
+target: research-wiki/designs/2026-07-15-question-gen-v2-grounded-contrastive-design.md
+---
+
+# question-gen v2 设计对抗审核
+
+**方法**:6 路独立审核官(5 Claude + 1 Codex),互不通气,各自站"找漏洞"立场对抗式核验。5 路死磕 finding 四层病灶各一层 + QuestionUnit 契约一致性;1 路 Codex 端到端质疑质量。
+
+**总判定**:**设计不能保证出题质量,不应直接进 writing-plans。** 四层病灶无一层 CLOSED;②不可翻转侧与契约层各带 Critical 级结构性缺陷(设计返工,非实现补丁)。
+
+## 四层闭合度矩阵
+
+| 层 | 判定 | 最致命残留 | 来源 |
+|----|------|-----------|------|
+| ① grounding 循环自证 | PARTIALLY | 换独立 VLM 只断"同模型自评"一半;打分仍黑盒标量、无帧证据可溯源(I1 自认)。时序逆序干扰项穿透 grounding/NLI/快解/后置四门 | ①+Codex |
+| ② 结构性捷径 | **不可翻转侧 OPEN/Critical** | 数学必然:单维反事实互不重叠→正解=逐维众数→逐槽投票 100% 命中,且满足全部硬约束。病灶②被从"偏置"升级成"数学保证" | ②+Codex |
+| ③ 歧义控制 | PARTIALLY(实操近 OPEN) | 现网 gate 仍 Phase A 松绑版且 multi_true 判官 text-only 不看帧;镜像模式引入两条新歧义路径(翻转轴模糊、搬运干扰项未复检) | ③ |
+| ④ 对抗信号位置 | PARTIALLY | 前移的是"便宜替身",完整 agent 仍后置且单裁判;回灌环无一致性校验+无收敛上界(踩 findings 漂移警告) | ④ |
+| 契约一致性 | **BROKEN** | 设计只覆盖采样/分批/聚合 3 处,真实代码另有 5+ 处按单题拆 pair;镜像"即用即弃"架构与"生成期双入库"互斥=重写非改造 | 契约 |
+
+## 跨审核官强收敛根因(高置信)
+
+| # | 根因 | 指认者 | 硬伤 |
+|---|------|-------|------|
+| R1 | **MiniMax 单模型多角色=系统性单点** | ①③④+Codex | grounding打分+快解探针+歧义门三合一;仅歧义门加 NLI 第二路,grounding/快解裸奔→某类视频系统性偏差三门同向失效。缺 AdVQA 多裁判一致性门 |
+| R2 | **不可翻转 4 子模式结构性未修** | ②+Codex | 逐维众数数学证明;覆盖 AR 题 60-70% 主体 |
+| R3 | **契约拆 pair 远超设计覆盖的 3 处** | 契约+Codex | pools.py 三池切分把 P/Q 劈到不同池→AR pair 几乎全变孤儿被剔;correctness 逐题 dict 与 pair 双向 AND 口径无法共存→污染进化 |
+| R4 | **验收指标自我确认 + 缺结构性/人工指标** | 全部+Codex | I4 测"模型是否用了捷径"非"捷径是否结构存在";MiniMax 相关指标全同源自证。缺帧错配安慰剂测试/逐维众数命中率(纯算法)/人工双正解率κ/槽位卡方/pair 关系可解率 |
+| R5 | **回灌环+补生成 recall 泄漏** | ③④ | 回灌无一致性校验+无轮次上限→漂移;补生成"重试直到通过"+门非确定+无 quarantine→precision-over-recall 稀释回 recall |
+
+## 逐层高severity 残留(矩阵未尽项)
+
+### ① grounding
+- V1 打分仍 `list[float]` 黑盒,observation 无帧证据/rationale/帧号(`distractor_selector.py` `_score_options`)——换 MiniMax 不改此数据结构。
+- V2 粗档下区间选择退化为平局任意 top-3,near-miss 甜区失效;扩池无梯度救不了。
+- V4 快解探针非对称谬误:弱解题器"解不出"被当"题够硬"绿灯。
+- V5 时序逆序干扰项被 NLI 判非等价放过、被时序弱的 MiniMax 判非双正解放过。
+
+### ② 结构性捷径
+- C1(Critical)逐维众数:见 R2。修法=放弃 hub-and-spoke,改平衡区组/因子设计(每维每值出现次数相等→逐维众数无定义),加纯算法硬校验 `argmax(逐维众数)==正解→fail`。
+- C2(高)镜像对 flip 反相关把双向 AND 从 1/16 打回 1/1(单个"事件时序"文本线索通杀 P+Q);bag-of-words 是"尽量"软约束未强制。
+- C3/C4/C5(高/中高)evidence_gap 的"无法确定"恒定文本靶、semantic_rigidity 字幕匹配反向信号、premature 最终段文本靶——单维反事实改不掉这些语用/类别差异。
+- C6(中)恒 A 正解槽位偏置无约束(Codex 亦独立指认 I1)。
+- C7(中)NLI 的 embedding 回退是假绿灯,测不出语义泛化蕴含。
+
+### ③ 歧义控制
+- R1 视觉双真+文本独立(拿杯子/放盘子同框):NLI 放行,视觉侧唯一裁判 MiniMax 漏判即穿门。
+- R2/R3(高,v2 新引入)镜像翻转轴模糊→P/Q 各自可辩护;搬运进 Q 的干扰项未在翻转题干下重新 grounding 复检。
+- R6(实操高)现网 `gate_multi_true.md` 仍松绑版;`_gate_multi_true` text-only 不看帧,"视觉双真"结构上无输入通道。
+
+### ④ 对抗信号位置
+- R1 便宜探针能挡的题 ⊂ 完整 agent 能挡的题,差集=结构性/多步 shortcut,仍靠后置。
+- R2 回灌环缺一致性校验+无 `max_regen_rounds` 上界。
+- R4 后置难度判定单裁判,缺 AdVQA 多裁判一致性;"agent 恰好能解/不能解"的偶然性主导难度标签。
+
+### 契约(BROKEN,设计未覆盖的 5+ 处)
+
+| 处 | 文件/函数 | 拆 pair / 错口径 | 严重度 |
+|----|----------|-----------------|-------|
+| 三池 progressive exclusion | `pools.py::build_pools`/`_sample_excluding` | 拆 pair 到 test/val/diag→几乎全变孤儿 | Critical |
+| per-category train/val 切分 | `pools.py::_split_one_category` | 拆 pair 到 train/val | Critical |
+| batching 对/错桶分离(先于 FFD) | `batching.py::_select_mixed_by_task_type` | 按逐题 correctness 把 P/Q 分进 correct/error 桶并抽样丢弃 | Critical |
+| gate 信息量阶梯 | `gate_ladder.py`+`runner._run_gate_validation` | 逐题跑 gate,双向 AND 不生效,逐题分污染 e-process/进化 | High |
+| pools.json 冻结/解冻 | `pools.py::_q_to_dict`/`_dict_to_q` | 丢 pair 三字段→孤儿 | High |
+| 作弊门逐题剔除 | `adversarial_filter.py::run_cheater_gate`/`write_final_bank` | 半剔成孤儿 | High |
+| correctness dict 逐题键 | `runner` 6+ 处消费 | pair 双向 AND 无处安放,与逐题 predictions 口径不自洽 | High |
+| 镜像即用即弃架构 | `adversarial_filter.py::generate_mirror_question` | `_mirror`后缀/临时 pair_id/不入库,与"生成期双入库"互斥=重写非改造 | High |
+| 非 AR byte-identical | 混合池 `rng.sample` | unit 折叠改变 population 长度→非 AR 选择漂移,破坏铁律 | Medium-High |
+
+## 必须返工的设计条目(按优先级)
+
+1. **§4.1.B 推倒重来**(R2, Critical):hub-and-spoke → 平衡区组/因子设计 + 逐维众数硬校验。
+2. **§4.2/§4.4 补第二独立视觉裁判 + 修快解探针非对称**(R1, Critical):grounding 打分带帧证据核验 + 第二 VLM 交叉(qwen/MiniMax 多模态看帧,非 CLIP 几何);快解探针要求 solver 先证胜任;歧义门补第二视觉裁判凑多裁判。
+3. **§5 契约大幅扩写**(R3, Critical):采样上移 pools.py(三池/train-val 以 unit 为原子);新增 unit 粒度 correctness 视图与逐题 dict 分离;序列化补 pools.json;作弊门 unit 粒度剔除;gate_ladder pair 口径。
+4. **§2/§4.5 如实标注镜像是重写**(R3):给持久 pair 落库 schema。
+5. **§4.4 multi_true AR 变体吃帧**(③):VLM 看真实帧;先落地收紧 rubric;镜像对增设翻转轴清晰度门 + 干扰项翻转题干下重新 grounding。
+6. **§4.3 回灌环加独立一致性校验+收敛上界;补生成加 quarantine**(R5)。
+7. **§4.1 I4 验收指标重构**(R4):补纯算法结构性指标 + 人工/异构审计 + 帧错配安慰剂测试。
+
+## 关键约束修正
+
+- **grounding 不需要 CLIP**:排除的是专用图像嵌入模型(CLIP/SigLIP 太难装配);VLM 多模态(qwen/MiniMax 直接看帧+文本)可用。故"第二独立视觉裁判 / multi_true 吃帧 / grounding 帧证据核验"均可用 VLM 多模态落地,只是拿不到几何相似度。
+
+## 反自证验收指标清单(R4 落地)
+
+| 指标 | 靶向 | 手段(须非同源自证) |
+|------|------|---------------------|
+| 帧错配安慰剂测试 | ①虚假 grounding | 喂错误视频帧打分,分数须显著坍塌 |
+| 逐维众数命中率 | ②C1 | 纯算法机械投票,阈值≈0.25 |
+| 答案槽位卡方 + pair 槽位反置率 | ②C6 | 纯统计 |
+| pair 关系可解率 | ②C2 | P+Q 一起喂盲答器+时序排序规则解法,须≈1/16 |
+| 双正解率 + 裁判间 κ | ③ | 双 VLM 视觉裁判 + 求解器分歧探针三源投票,基线 33%→阈值≤5% |
+| 难/烂混淆矩阵 | ④ | 人工/强裁判双标签,对照纯 Phase B 基线 |
+| 难度判定翻转率 | ④R4 | N 次独立 agent 试答,测判定稳定性 |
+| 前移拦截覆盖率 | ④R1 | 后置剔除题里"本应生成端拦下"占比 |
+
+## 相关
+- 设计: [2026-07-15-question-gen-v2-grounded-contrastive-design.md]
+- 诊断: [2026-07-15-question-gen-v2-diagnosis-and-strategy.md]
diff --git a/research-wiki/schemas/v3-question-gen-logging.md b/research-wiki/schemas/v3-question-gen-logging.md
new file mode 100644
index 0000000..1123ea0
--- /dev/null
+++ b/research-wiki/schemas/v3-question-gen-logging.md
@@ -0,0 +1,88 @@
+---
+id: v3-question-gen-logging
+title: 表结构 v3 出题日志/观测(unit_verdict / collapse_metrics / quarantine / facts / resume)
+type: schema
+created: 2026-07-15
+---
+
+# v3 出题运行时数据 schema
+
+**载体**:扩展 `app/question_gen/run_store.py`(同一 SQLite,不新建 run_store_v3)。所有 LLM/VLM 调用 telemetry 走 `GovernedLLMClient` 自动录(CLAUDE.md §4.8),本 schema 只管**出题领域观测**。落库统一 `store.insert(...)`,禁裸 SQL 散落。
+
+## 表 1:`facts`(帧感知抽取 Fact schema,§9)
+
+| 列 | 类型 | 说明 |
+|----|------|------|
+| fact_id | TEXT PK | UUID |
+| video_id / segment_id | TEXT | 溯源 |
+| subject / action / object | TEXT | 结构化绑定 |
+| frame_ids | TEXT(JSON) | 感知所用帧 |
+| polarity | TEXT | 真/假 |
+| fact_type | TEXT | binding/state/manner/order/evidence |
+| difficulty_tier | INT | 感知难度分层(C1;manner/计数=高)|
+| verifier_refs | TEXT(JSON) | qwen/MiniMax 各自裁决 |
+| cross_agree | INT | 双 VLM 是否一致 |
+| negative_at_target | TEXT | 目标点为假的核实结果(C5)|
+| session_id | TEXT | epoch/step 关联 |
+
+## 表 2:`unit_verdict`(六层验证每层裁决,§6)
+
+| 列 | 类型 | 说明 |
+|----|------|------|
+| unit_id | TEXT | pair/single 单元 |
+| pair_id | TEXT? | pair 关联 |
+| sub_pattern | TEXT | 6 子模式之一 |
+| stage | INT | 层 1-6 |
+| verdict | TEXT | pass/fail/abstain |
+| reason | TEXT | 拒因 |
+| metric_value | REAL? | 该层量化值 |
+| model | TEXT? | 裁判模型 |
+| session_id | TEXT | |
+
+## 表 3:`collapse_metrics`(配对坍缩度量,§7)
+
+| 列 | 类型 | 说明 |
+|----|------|------|
+| pair_id | TEXT PK | |
+| text_only_acc / single_frame_acc / placebo_drop | REAL | 模型探针类 |
+| majority_vote_hit / slot_chi2 / distractor_min_dist | REAL | 纯结构类 |
+| multiformat_consistency / subtitle_answerability | REAL | 探针类 |
+
+## 表 4:`quarantine`(失败题指纹去重,§10)
+
+| 列 | 类型 | 说明 |
+|----|------|------|
+| content_fingerprint | TEXT PK | 题面语义内容指纹 |
+| sub_pattern / quarantine_reason / round_no | TEXT/TEXT/INT | |
+| ts | TEXT | 时间戳(外部传入,禁进程内 now)|
+
+## 表 5:`resume_state`(断点续跑,§12)
+
+| 列 | 类型 | 说明 |
+|----|------|------|
+| unit_id | TEXT PK | |
+| status | TEXT | pending/accepted/rejected |
+| config_fingerprint | TEXT | 失效检测(求解器/裁判 config 变更作废)|
+| seq_offset | INT | 补构造续编防撞 |
+
+## 基线指标(进科研 YAML + run 快照,首次运行后建基线)
+
+| 指标 | 阈值/口径 | 类别 |
+|------|----------|------|
+| 逐维众数命中率 | ≈随机(0.25) | 纯结构(硬地锚)|
+| pair 关系可解率 | ≈1/16 | 纯结构 |
+| 双正解率 | 基线 33%→≤5% | 探针+人工 |
+| 各门通过率 / backfill_yield_by_round | 观测,防减产不可行区 | 系统 |
+| judge_disagreement_by_subpattern / shared_error_rate | 观测(人工小样本估)| 探针+人工 |
+| negative_at_target 剔除率 | spike 实测 ~17%(基线)| 结构 |
+
+## 埋点位置(交 writing-plans,必须作显式步骤)
+
+| 埋点 | 文件/函数 | 调用 |
+|------|----------|------|
+| Fact 落库 | `grounded_fact_extractor`(新建) | `store.insert_fact(...)` |
+| 逐层裁决 | `pipeline`(主编排)每层出口 | `store.insert_unit_verdict(stage, verdict, reason, ...)` |
+| 坍缩度量 | `collapse_metric`(新建) | `store.insert_collapse_metrics(...)` |
+| quarantine | `pipeline` 拒题路径 | `store.quarantine(content_fingerprint, reason)` + 补构造前查黑名单 |
+| 续跑状态 | `pipeline` on_accept/on_reject | `store.upsert_resume_state(...)` |
+| LLM/VLM telemetry | 所有 VLM/LLM 调用 | GovernedLLMClient 自动(session_id 透传)|