From 63fd213ab57fe0be987b59a4674b1b02c2a1ea1c Mon Sep 17 00:00:00 2001 From: shaofl <2899218482@qq.com> Date: Fri, 31 Jul 2026 10:54:49 -0700 Subject: [PATCH] docs: record the D5 C6 close-out C6 in two PRs, per the owner's ruling that the conversion and the deletion be separated and each carry its own review: #115 retired the three regeneration tools to verify-only, #116 deleted the eleven legacy runners and wired verification into the three sites where the C5 harness had been reading frozen inputs unchecked. Decision (3) - kill the second factor data path - closes here. What is worth carrying forward is not the deletion. Three things were established by measurement rather than by assertion: The wiring is real. Reverting a read site to a bare path, replacing it with an inline path that never calls the gateway, and keeping the call but swallowing its exception are three distinct ways to bypass the check, and each has a test that goes red. The third one is the reason the record side uses a behaviour test rather than an AST assertion: AST can only show the line is present. A manifest's claim was narrowed to what it actually blocks, in two rounds. It was first written too wide - it promised to catch a case that can no longer occur on the pinned record, because the loss already happened and what is pinned is the state after it. Then it was written too narrow, missing a boolean verdict flag that is neither a count nor a key yet is compared. The narrow wording came from the lead relaying a reviewer phrasing; the implementer rejected it on measurement, on the grounds that copying it would build the same defect into the fix. The test that asserts the remaining limitation is deliberately built to go red if anyone strengthens the check, so the declaration cannot silently become wider than the code again. And the frozen directory turned out not to be immutable. Seven of its 113 files are covered by no hash at all, and the one that is genuinely unprotected is the one that was actually overwritten, in July. The rule now states what it can honestly state: "no bytes were written" is a claim about an operation, not about the directory. The same protocol hole recurred three times: the tree was modified while a reviewer was measuring it. Neither occurrence was caught by a test; both were caught by an observation that contradicted an expectation. Closing it took one rule on each side - the lead waits for every item including appended ones, the reviewer states whether it has finished, and the implementer does not resume after declaring it has stopped, not even for a defect it found itself. Neither contamination cost a round, because the scoping was done by which text a measurement read rather than by commit timestamps, and because "no executable change" was measured as AST equality after stripping docstrings. --- AGENTS.md | 8 ++++---- CLAUDE.md | 8 ++++---- docs/progress/07_factor_layer_refactor.md | 12 ++++++++++++ docs/progress/pr_ledger.md | 3 +++ 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 8e7f61d..a80c47d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -66,15 +66,15 @@ data → universe → factors(特征) → alpha(合成/预测) → portfolio(+ri | 项 | 值 | |---|---| -| `main` | 下表全部 gate 实测于 `a5bbbba`(PR #113,D5 C5 收口) | -| `pytest -p no:warnings` | **2606 passed**(⚠️ 不要再传 `-q`:`pyproject.toml` 已含 `addopts="-q"`,叠成 `-qq` 会吞掉摘要行) | +| `main` | 下表全部 gate 实测于 `4e73ac1`(PR #116,D5 C6 收口) | +| `pytest -p no:warnings` | **2669 passed + 1 skipped**(⚠️ 不要再传 `-q`:`pyproject.toml` 已含 `addopts="-q"`,叠成 `-qq` 会吞掉摘要行) | | `ruff check .` | clean | | phase0 锚(`run-phase0 --config config/example.yaml`) | **ic 0.9600 / annual 0.8408**(自 P0 起从未变过,任何改动都不许动它) | | `qt.exec_baseline_freeze --verify` | **77/77 @ `45c14aa`** | | `validate-config` | 32/32 | | 唯一 OPEN 的 PR | #38(历史遗留,保持 OPEN) | -**当前主线 = 因子层深度重构(设计 v3.2,D0–D7)。D5 C5 已收口(PR #113):11 因子 × 3 模式 33 格全绿,C5 通过 ⇒ 满足 C6 前置条件之一。** F1 残桶前视缺陷另立 correctness PR #112 修复(panels `unclassified` 729,029 → 0)。**下一步 = Step 4 / C6:删 11 个旧 runner**(owner 2026-07-28 已裁定退役重生成能力;前置条件另有 R16 覆盖数非降的逐条核销,且改造 PR 与删除 PR 必须分开)。 +**当前主线 = 因子层深度重构(设计 v3.2,D0–D7)。D5 已全部收口**:C5 四腿对账 33 格全绿(#113),**C6 完成(#115 改造 + #116 删除)——11 个旧 runner 归零,决策③「杀第二条因子取数路径」收尾**,三个重生成工具退役为 verify-only、D1 基线 frozen-forever、校验已接进 C5 harness 的三个读入点。**下一步 = Step 5 / D6a–d 存量收口**(`aggregate → factors.minute` 的 shim 日落、`asof_daily_features` 的 `mmp_ew` hook 需要新家),然后 D7 收官全量重评估(**exec-only**)。 > **接手必读(唯一入口)**:[`tmp/design/HANDOFF_2026-07-30_claude_code.md`](tmp/design/HANDOFF_2026-07-30_claude_code.md) > ——含当前截面、F1–F5 裁定速查表、六步路线、派发/评审/限额的具体操作法、陷阱清单。 @@ -146,6 +146,6 @@ data → universe → factors(特征) → alpha(合成/预测) → portfolio(+ri ## 路线图 -1. **因子层重构收尾(当前主线)**:~~F1 残桶 correctness PR (#112)~~ → ~~C5 audit PR (#113)~~ → ~~C5 进度文档~~ → **C6 删 11 个旧 runner(下一步)** → D6a–d 存量收口 → D7 收官全量重评估(**exec-only**)。 +1. **因子层重构收尾(当前主线)**:~~F1 (#112)~~ → ~~C5 audit (#113)~~ → ~~C5 进度文档 (#114)~~ → ~~C6 改造 (#115) + C6 删除 (#116)~~ → **D6a–d 存量收口(下一步)** → D7 收官全量重评估(**exec-only**)。 2. **因子研究**:十一因子无一到 Adopt ⇒ 下一步优先做**换手/成本敏感的组合层验证**与更长 holdout,而不是继续堆新因子;补 PR #79 的 review;修订 HTML compendium。 3. **可选、须单独显式 goal**:I5g(强制 partial-fill / volume-cap);数据层 D6(`PanelStore` append/partition,仅当因子研究需要可复用派生面板时才启动);schema 守卫接 live `data-update`。 diff --git a/CLAUDE.md b/CLAUDE.md index 8e7f61d..a80c47d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -66,15 +66,15 @@ data → universe → factors(特征) → alpha(合成/预测) → portfolio(+ri | 项 | 值 | |---|---| -| `main` | 下表全部 gate 实测于 `a5bbbba`(PR #113,D5 C5 收口) | -| `pytest -p no:warnings` | **2606 passed**(⚠️ 不要再传 `-q`:`pyproject.toml` 已含 `addopts="-q"`,叠成 `-qq` 会吞掉摘要行) | +| `main` | 下表全部 gate 实测于 `4e73ac1`(PR #116,D5 C6 收口) | +| `pytest -p no:warnings` | **2669 passed + 1 skipped**(⚠️ 不要再传 `-q`:`pyproject.toml` 已含 `addopts="-q"`,叠成 `-qq` 会吞掉摘要行) | | `ruff check .` | clean | | phase0 锚(`run-phase0 --config config/example.yaml`) | **ic 0.9600 / annual 0.8408**(自 P0 起从未变过,任何改动都不许动它) | | `qt.exec_baseline_freeze --verify` | **77/77 @ `45c14aa`** | | `validate-config` | 32/32 | | 唯一 OPEN 的 PR | #38(历史遗留,保持 OPEN) | -**当前主线 = 因子层深度重构(设计 v3.2,D0–D7)。D5 C5 已收口(PR #113):11 因子 × 3 模式 33 格全绿,C5 通过 ⇒ 满足 C6 前置条件之一。** F1 残桶前视缺陷另立 correctness PR #112 修复(panels `unclassified` 729,029 → 0)。**下一步 = Step 4 / C6:删 11 个旧 runner**(owner 2026-07-28 已裁定退役重生成能力;前置条件另有 R16 覆盖数非降的逐条核销,且改造 PR 与删除 PR 必须分开)。 +**当前主线 = 因子层深度重构(设计 v3.2,D0–D7)。D5 已全部收口**:C5 四腿对账 33 格全绿(#113),**C6 完成(#115 改造 + #116 删除)——11 个旧 runner 归零,决策③「杀第二条因子取数路径」收尾**,三个重生成工具退役为 verify-only、D1 基线 frozen-forever、校验已接进 C5 harness 的三个读入点。**下一步 = Step 5 / D6a–d 存量收口**(`aggregate → factors.minute` 的 shim 日落、`asof_daily_features` 的 `mmp_ew` hook 需要新家),然后 D7 收官全量重评估(**exec-only**)。 > **接手必读(唯一入口)**:[`tmp/design/HANDOFF_2026-07-30_claude_code.md`](tmp/design/HANDOFF_2026-07-30_claude_code.md) > ——含当前截面、F1–F5 裁定速查表、六步路线、派发/评审/限额的具体操作法、陷阱清单。 @@ -146,6 +146,6 @@ data → universe → factors(特征) → alpha(合成/预测) → portfolio(+ri ## 路线图 -1. **因子层重构收尾(当前主线)**:~~F1 残桶 correctness PR (#112)~~ → ~~C5 audit PR (#113)~~ → ~~C5 进度文档~~ → **C6 删 11 个旧 runner(下一步)** → D6a–d 存量收口 → D7 收官全量重评估(**exec-only**)。 +1. **因子层重构收尾(当前主线)**:~~F1 (#112)~~ → ~~C5 audit (#113)~~ → ~~C5 进度文档 (#114)~~ → ~~C6 改造 (#115) + C6 删除 (#116)~~ → **D6a–d 存量收口(下一步)** → D7 收官全量重评估(**exec-only**)。 2. **因子研究**:十一因子无一到 Adopt ⇒ 下一步优先做**换手/成本敏感的组合层验证**与更长 holdout,而不是继续堆新因子;补 PR #79 的 review;修订 HTML compendium。 3. **可选、须单独显式 goal**:I5g(强制 partial-fill / volume-cap);数据层 D6(`PanelStore` append/partition,仅当因子研究需要可复用派生面板时才启动);schema 守卫接 live `data-update`。 diff --git a/docs/progress/07_factor_layer_refactor.md b/docs/progress/07_factor_layer_refactor.md index 4c115ff..f6a2894 100644 --- a/docs/progress/07_factor_layer_refactor.md +++ b/docs/progress/07_factor_layer_refactor.md @@ -47,3 +47,15 @@ - **重跑射程是实测不是论证**:扰动 `qt/factor_eval_reconcile.py` 后重算 5 个因子 `code_hash` → **5/5 逐字不变**;**负对照**扰动 `factors/compute/minute/primitives.py`(在 shared set 内)→ **5/5 全变** ⇒ 探针非空、harness 在 code_hash 射程之外、store 不失效。 - **已登记、本 PR 不改**:`_make_logger` truncate;jump 的 reports 腿对**值改动**实际不设闸(593 个 diff 里 **518** 个走既有更正承载登记 #11、`with_book(decision)` 是 report-only #13 ⇒ 值级依据应看 `no_book`/`bookclose` 两个 strict 格与 panels 腿,**结构性增删仍设闸**)。 - gates(lead 独立重跑):pytest **2606 passed** / ruff clean / phase0 锚 **0.9600·0.8408** / **32/32** configs / `--verify` **77/77 @ 45c14aa** / secret **0** / 陈旧措辞 grep **0** / 审计报告 `TBD` **0** / 编目对 main **纯追加(0 删除行)** / 冻结基线**今日 0 个文件被写**。 +- ✅ **D5 C6(PR #115 改造 + #116 删除,owner 2026-07-28 裁定「退役重生成能力」;两个 PR 分开、各带评审)**:**决策③「杀第二条因子取数路径」到此收尾——11 个旧 runner 归零。** + - **改造(#115)**:`qt/panel_freeze.py` / `qt/panel_reconcile.py` / `qt/hand_anchors_engine_values.py` 的重生成恒 raise `RegenerationRetiredError`(**旧参数仍被 parser 接受**,否则用户拿到的是 `unrecognized arguments` 而非退役说明),验证路径保留并加强:panel_freeze 新增**以前不存在的一条路**——逐面板重算 canonical hash + 整条 manifest 行,对 **git 里的**哈希表核,gitignored `manifest.json` 作第二见证互核(实跑 15/15);新增 `qt/frozen_manifest_doc.py`(文档表解析器)。`qt/cli.py` **11 个 `run-eval-*` 收敛到 `run-factor-eval`**,映射**从 `FACTOR_TO_REPORT_NAME` 派生**(评审进程内扰动该表验证:旧名失效、新名生效)。**R16 59 条逐条核销通过 ⇒ 删除的准入证**。 + - **删除(#116)**:删 **11 个 `qt/eval_*.py` + 10 个 `tests/test_eval_*_runner.py`**(`test_eval_contract_v1.py` / `test_eval_figures.py` **不是** runner 测试,保留)。可达性含动态形式(`importlib`/`__import__`/字符串拼名/glob)自查 + 评审独立复核。**node-ID removed 59,落在被删文件之外 0 条**;commit 措辞 "GONE WITH THE CAPABILITY … claims no replacement",**不为其中任何一条声称有测试替代**。R16 删前删后 **56/56 覆盖非降**;审计脚本删后 rc=1 **只**来自它 [1]/[2] 的删前口径,**没有把脚本改绿**(那会掩盖这个区分,§六.16 用在审计脚本上)。 + - **把校验接进 C5 harness 的三个裸读点**:`run_panels_mode`(冻结 D1 面板)/ `run_anchors_mode`(**`--mode anchors` 单跑时唯一的冻结面板读入点**)/ `load_anchor_rows`(anchors JSON)。面板侧 `verified_frozen_panel_path` 成为**唯一网关**(AST 强制),**按因子校验而非整棵扫**(0.2s vs 3.0s——为读 1 个而验 15 个,会把校验变成大家去关掉的东西)。**jump 分叉承重**:两棵树的 `jump_amount_corr_20.parquet` 是**两个不同定义**(v1.0 未截断 / v1.1 已截断,canonical `b6359f12…` vs `fafda485…`),路由**按路径读、不从 factor id 重推**;评审实测**拿 D1 的期望核 PR-C 字节 → 正确拒绝**。 + - **顺带查出冻结目录并不是不可变的**:普查 113 个文件,`exec_baseline` 77 + `panels` 14 + `panels_d2` 14 + `pr_c` 1 有 git 哈希覆盖,**7 个没有**(分级:3 个 `manifest.json` 字节未钉但内容三方互核 / 3 个 `.md` 可推导 / **只有 `hand_anchors_d2.json` 真正无保护**)。**而它恰是唯一被实际覆盖过的**——2026-07-25 一次 `hand_anchor_rows` 重跑冲掉 engine 比较记录(现 20 pending / **0 compared**),填回它的工具本轮退役 ⇒ `hand_anchors --verify` **永久红**(措辞 `NOT VERIFIED (nothing recorded)` 与 FAILED 区分、**未进任何 gates 清单**)。**丢的是可再验证性,不是结论**(D2 手算锚「88 行 0 失配」仍在 PR #89 与本文件在案)。⇒ 编目写明:**「冻结基线一个字节未写」是关于某次操作的陈述,不是关于该目录不可变的陈述。** + - **anchors 记录的 manifest 射程,经两轮才写准(本轮最值得留的一段)**:新增 git 里的 `docs/factors/d5_hand_anchor_record_manifest.json` 钉 sha + 清单。**先写宽了**——声称能抓「`daily_engine_compared` 悄悄归零」,而**那个 case 在当前 pin 状态上已不可能再发生**(损失已发生,被 pin 的是损失之后的状态,那个字段**已经在地板上**)。**改窄后又写窄了**——写成"抓行数/键集变化",而 `all_ok_frozen14` 是**布尔判定标志**、既非计数也非键、**却一直在被比较**(⚠️ 这句偏窄的措辞**是 lead 转述的**,实现方**按测量否掉了它**,理由"照抄会把同一个缺陷装进 fix 里")。**最终射程 = 形状(三个计数 + 键集 + 判定标志)**,豁免重述为**"形状未变"而非"重跑这个动作"**。评审在真实 pin 形态跑**六条带 6/6 与措辞一致**,并实测**动机形态的真实方向**(manifest 若 pin 在记录仍有 20 行时、记录随后丢掉 + 刷新 sha → **REFUSED**)⇒「当初若 pin 得早就能抓住」是**跑出来的不是推理**。**那条"承认限制"的测试被刻意做成会在检查被加强时变红**——评审真加了第 6 个 inventory 字段验证,**它红了** ⇒ **声明不可能再次比代码宽**。 + - **`hand_anchor_rows` 不再静默覆盖冻结记录**:目标已存在即拒绝,除非显式 `--allow-overwrite`;拒绝信息按**差集**算出"这次跑不会重新产生的键";**检查前移到 ~7 分钟计算之前**(在长跑结束才拒绝,教出来的是反射性加 override)。**明确否决 D-3**(读回旧文件 carry forward 自己不产生的键):会把**对着旧引擎值算出的**比较结果与**刚重算的**值拼进同一文件 ⇒ **内部不自洽的记录,比诚实的缺失更糟**;**因为它很诱人所以写进案卷**(禁翻烧饼)。 + - **`.gitignore` 规则终于表达了它一直想表达的意思**:`git add -A` 曾把一个**指向本机绝对路径的 `artifacts` 符号链接**提交进来——**被 `test_frozen_bytes_are_untracked_while_the_manifest_is_tracked` 抓住**(该测试守的正是整个校验设计赖以成立的 git/gitignore 分家:字节与哈希若都在 git 里,一个 commit 就能把两者一起挪走)。根因 `artifacts/` **带斜杠**既不匹配符号链接也不匹配 `artifacts_local_phase0/`(**后者无人守**)⇒ 改为 `artifacts`(无斜杠)+ 新增 `artifacts_local_phase0/`;**代价写在规则旁:那两个路径从此在 `git status` 里不可见。** + - **评审最值得记的一击**:**「只钉函数、没钉接线」这个洞,在三个读入点里活下来一个。** 面板侧修好后有两道守卫,而记录侧六条测试把 `verify_anchor_record` 这个**函数**测得很扎实、**却没有一条说 `load_anchor_rows` 必须调用它** ⇒ 把那行换成 `pass`,**全量套件 2661 passed / rc=0、与未变异树逐数相同**。实现方原话:**「一个只在学到它的地方被应用的教训,等于没被应用。」** 修法用**行为测试承重而非 AST**,并把理由做成 **mutation #24**:保留调用、只吞掉异常 ⇒ **AST 通过、行为测试红** —— **把"优先行为"从论断变成了可复跑的性质**。 + - **方法论新增(本轮换来的)**:① **一个失败的 mutation 逼出了更好的设计**——首版针对接线的 mutation 没牙(只钉"路由"没钉"接线"),正是它促成单一网关重构;② **"把自己的守卫说弱"比"说强"更难被自己发现**——说强会被 mutation 打红,**说弱不会;没有任何 mutation 会因为文档太谦虚而失败** ⇒ 只能靠**拿措辞去对实测行为**,且必须由**别人**做;③ **控制组必须先 ACCEPTED**,否则后面每个 REFUSED 都是空的(与"mutation 必须先断言落地"是同一道理的两面)。 + - ⚠️ **同一个协议洞复发三次**(评审两次发现**树在它测量期间被改动**:一次因 lead 收到主报告就派返工而评审仍有追加项、一次因实现方声明停手后自审出真缺陷**直接改了**)。**两次都是"与预期矛盾的观察"抓住的,不是测试。** 三方各补一条才闭合:**lead** 等全部条目交付且明确收工才派返工;**评审**每份报告显式声明是否收工;**实现方**声明停手后非经指派不得恢复工作——**哪怕自己发现真缺陷,也先报告再动手**。**两次污染都被逐条厘清射程而非笼统作废,无一轮重来**,靠两条定界规矩:**刻度是「哪次测量读了哪份文本」不是 commit 时间戳**(工作树**先于** commit 改变,实证一次探针跑在 commit 前 90 秒却已读到新文本);**「没动可执行代码」要用剥掉 docstring 后的 `ast.dump` 精确比来量**。 + - gates(lead 独立重跑):pytest **2669 passed + 1 skipped** / ruff clean / phase0 锚 **0.9600·0.8408** / **32/32** configs / `exec_baseline --verify` **77/77 @ 45c14aa** / 三个重生成入口 **rc=1**(loud)/ 两个 `--verify` OK / 旧 CLI 名 **rc=1** + 迁移提示、新名 rc=0 / `git add -A --dry-run` 无可暂存项 / secret **0** / 冻结基线**自 2026-07-25 起 0 写入**。实现方 mutation **31/31 有牙**,评审两轮各自独立写 mutation 复现关键项。 diff --git a/docs/progress/pr_ledger.md b/docs/progress/pr_ledger.md index 00b9688..b87a335 100644 --- a/docs/progress/pr_ledger.md +++ b/docs/progress/pr_ledger.md @@ -21,3 +21,6 @@ | #112 | `amp_marginal_anomaly_vol` 残桶 correctness fix(单独成 PR,非重构一部分) | | #113 | D5 C5 四腿全量对账收口(F2/F3/F4 扩类 + F5 修复 + NIT-1 + 审计报告) | | #114 | 进度文档(F1 + D5 C5) | +| #115 | D5 C6 改造:三个重生成工具退役为 verify-only + CLI 收敛 + R16 核销 | +| #116 | D5 C6 删除:删 11 个旧 runner + 10 测试,校验接进 C5 harness 三个读入点 | +| #117 | 进度文档(D5 C6) |