feat(semantics): report the name-keyed value-set divergence a rename hides - #4614
huangruiteng merged 6 commits into
Conversation
…hides Collisions are keyed by name, so renaming one side of a multi-value fork removes the name from multi_value_forks and lowers the semantic budget while the drift stays in the tree. The RFC records this as a known limit and names the advisory merge report as the review aid, but that report groups *different* names carrying *identical* value sets, while a fork is *one* name whose modules disagree -- so it lists no fork at all. Measured on the baseline: all three undeclared forks (AGENT_TODO_HEADER_MARKERS, USER_TODO_HEADER_MARKERS, RAW_MATERIAL_KEY_HINTS) are absent from merge_candidate_groups, and renaming one side drops the semantic count 3 -> 2 with nothing reporting it. Add divergent_value_sets(inventory), the name-keyed companion printed by --report, and pin both behaviours as tests. Renaming one side still lowers the budget -- the limit is real and the test asserts it as documented so a future fix cannot land as a silent edit -- but the abandoned name now stays listed while its surviving definitions disagree. Renaming every side at once remains indistinguishable from an honest rename and is recorded as accepted residue; the RFC known-limits entry in both mirrors now states exactly that boundary instead of implying the merge-candidate report covers it. No budget or inventory count changes: the advisory is not a budget input. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: song <22676124+songoow@users.noreply.github.com>
…iance-evidence Signed-off-by: song <22676124+songoow@users.noreply.github.com>
自审(exact head
|
The Section 9 boundary change needs its decision-log row: what was added, what it does not close, and which alternatives were rejected. Records the value-set budget as rejected (CONFIDENCE_LEVELS and EDGE_CASE_COMPLEXITIES share high/low/medium with different meanings) and a committed name ledger as rejected at M0 (Q9 retired the committed census), so the residue is a decision rather than an omission. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: song <22676124+songoow@users.noreply.github.com>
The first version of this branch claimed divergent_value_sets catches a one-sided rename. Measurement disproves it: renaming one side leaves the name with a single definition, so it stops being a fork and drops out of the advisory exactly as it drops out of the budget. The earlier reading mistook a neighbouring unrenamed fork in the report for the renamed name. Correct the docstring, both RFC mirrors and the Appendix B row to state the boundary as it behaves, and pin all three cases as tests: a declared name rejects a one-sided rename (the declaration names every defining module), while an undeclared one-sided rename and a whole-name rename both lower the budget with nothing reporting them. The advisory's real value is narrower and now stated as such -- it lists surviving forks by name with their disagreement count, where they were visible only as a number before. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: song <22676124+songoow@users.noreply.github.com>
exact-head 复核(
|
| 改名情形 | 预算 | 是否有报告 |
|---|---|---|
已声明名字,单侧(SOURCE_SURFACES) |
不变 | 被拒绝 — scope_declarations 指明每个定义模块,被改名一侧不再匹配 |
| 未声明名字,单侧 | 下降 | 无任何报告 — 名字只剩一份定义,退出分叉判定 |
| 未声明名字,全侧 | 下降 | 无任何报告 — 与诚实改名不可区分 |
修正后这个 PR 的价值(更窄,但如实)
它不是改名检测器。它的实际作用是:把仍然存在的分叉按名字列出,并给出分歧值集数量——此前这些只能看到一个数字。这是"让检查可行动"的改进,而不是关闭 RFC 第 9 节那条限制。该限制仍未闭合,已作为决策(含两个被否决的替代方案)记入附录 B,而不是被含糊带过。
对主干的风险
observable_semantics:预算与清单计数零变化(multi_value_forks=4/4、semantic=3/3),advisory 不参与判定。测试断言的是一条已知限制的真实行为——若将来有人真正修好它而不同步 RFC,test_renaming_one_side_of_a_fork_launders_the_semantic_budget 会失败。
验证(本地,当前 exact head)
semantic-vocabulary-drift-smoke: ok
docs-governance-smoke ok
divergent_value_sets -> 4 forks listed (SOURCE_SURFACES value_sets=4)
3 条新测试逐个通过;变异验证:强制返回 [] 使 advisory 测试失败
Verdict: APPROVE (self-review) — do not self-merge (control-plane change, per #4586).
Residual risk: 未声明名字的改名仍不可测;本 PR 只如实记录并留测试哨兵,不声称闭合。
…iance-evidence Signed-off-by: song <22676124+songoow@users.noreply.github.com>
exact-head 复核(
|
…iance-evidence Signed-off-by: song <22676124+songoow@users.noreply.github.com>
本 PR 在 #4447 计划中的位置issue #4447 现在有一节统一协调(中英双语),把这 13 个在开 PR 作为一个计划列出:各自修什么、为何必要、以及实测出的合并顺序。 冲突实测:对全部 78 对做了试合并,9 对冲突,分四簇,每一处都是文本相邻,没有一处是语义分歧。
建议顺序(代价从低到高):#4628 → #4625、#4626 → #4627 → #4619、#4621 → #4630 → #4614 → #4631 → #4629 → #4617 → #4606 → #4608。四个棘轮 PR 放最后,因为每落地一个,下一个的数字就从估算变成确定值。 全部 13 个 PR 现已同步到 |
huangruiteng
left a comment
There was a problem hiding this comment.
详细评审 — exact head 5425a7d88fcfc9544e9e592dbba6998a9f37706f
动机
RFC 第 9 节把"改名洗白冲突"列为已知边界,并指名合并候选报告作为评审辅助;但那份报告归组的是不同名字、相同值集,而分叉是同一名字下模块互相分歧——两者问的不是同一个问题,所以它永远列不出任何分叉。作者用测量把这一点说清楚(基线下三个未声明的分叉都不在 merge_candidate_groups 里),方向是对的:要么修掉错误指路,要么补上真正能看到分叉的视角。
改动思路
两条一起做:新增只读的按名字归组的分歧报告 divergent_value_sets(inventory),由既有的 --report 打印(列出仍然存在的分叉、分歧值集数量与定义模块);同时把 RFC 第 9 节与附录 B 的措辞改成实测行为,而不是"应该怎样"。特别值得肯定的是第三个提交:初稿声称该报告"能抓住单侧改名",作者用测量否证了自己,并把 docstring、两份 RFC 镜像、附录 B 行与三条测试一起改成真实边界。这种"先写、再测、按测改文档"的闭环,正是这条 lane 一直在要求的纪律。
具体改动
loopx/semantics/inventory.py(+28→最终约 35):divergent_value_sets()读取duplicate_definitions.multi_value_forks,按名字去重值集、过滤掉不足两个值集的条目,按分歧数与名字排序输出;docstring 明确"不是改名检测器",且"不是第二权威、不提交、不进预算"。scripts/generate_semantic_inventory.py(+6):在--report分支新增value_sets / name / definition_modules段落,只在报告路径生效,默认生成与--output路径字节不变。tests/architecture/test_semantic_vocabulary_drift.py(+53→约 119):三条测试锁边界——test_renaming_one_side_of_a_fork_launders_the_semantic_budget(单侧改名确实让语义预算 3→2)、test_divergent_value_sets_lists_the_names_a_rename_would_hide(列表包含三个名字,且divergent_value_sets不在 inventory 里)、test_rename_visibility_splits_into_three_cases(已声明名字的部分改名Drift: every defining module;未声明的单侧改名与整体改名都从预算与报告中消失;存活分叉仍被列出)。- 两份 RFC(en / zh-CN)与附录 B:把边界写成实测结果(该报告不是改名检测器;唯一失败关闭的是已声明的名字;未声明单侧改名与整体改名都无人报告,属于 M0 接受的残余),并在决定日志里记录被否决的替代方案(按值集做预算、提交名字账本)及理由。
验证(都在本 head):tests/architecture/test_semantic_vocabulary_drift.py → 61 passed;scripts/generate_semantic_inventory.py --report --top 5 正常打印新段落(SOURCE_SURFACES 4 个值集、AGENT_TODO_HEADER_MARKERS/RAW_MATERIAL_KEY_HINTS/USER_TODO_HEADER_MARKERS 各 2 个,并列出定义模块);我另写探针复现了测试里的那次单侧改名:改名后该名字没有 fork 条目(定义数 None)、不在报告中,预算 4→3,与修正后的文档一致。
对主干的风险
无预算、清单或运行时契约变化:本 PR 不触碰 vocabulary_v0.json 的任何 ratchet,multi_value_forks 锚点仍是 4;报告是纯只读输出,测试显式断言它不出现在提交的 inventory 里,因此不会变成第二权威。文档层面的风险恰好被本 PR 自己消掉了——我原本准备提的"RFC 声称报告能抓单侧改名,但测试构造的恰恰是报告看不见的那种改名",作者已在第三个提交中用测量改正,并用三条测试把边界钉住;我独立复现确认修正后的描述与实际行为一致。唯一需要读者注意的仍是那条残余(未声明单侧 / 整体改名不可见),而它现在被写成决定而不是被遗漏。
我的整体评价
APPROVE。 这是一个"允许不如预期、但必须说准确"的改动:报告的真正价值(把存活分叉从数字变成带名字与模块的清单)被如实陈述,未闭合的边界同时在 RFC 正文、附录决定日志、函数 docstring 与测试注释四处对齐,且有测量支撑。合并权仍在维护者。
English verdict: APPROVE - reviewed exact head 5425a7d. The PR adds divergent_value_sets(inventory), a read-only name-keyed listing of surviving multi-value forks with their disagreement counts and defining modules, prints it in the existing --report branch, and corrects both RFC mirrors plus Appendix B so the rename limit is stated as measured rather than as assumed. Credit where due: the third commit disproves the branch's own first claim that the advisory catches a one-sided rename, and the final head documents that an undeclared one-sided rename and a whole-name rename both stay unreported while a declared name fails closed. I verified that independently: reproducing the test's rename of AGENT_TODO_HEADER_MARKERS in loopx/state_projection.py leaves the name with no fork entry, absent from the advisory, and drops the budget 4->3, matching the corrected text. Validation at this head: 61 passed in tests/architecture/test_semantic_vocabulary_drift.py, the report prints the new section (SOURCE_SURFACES 4 value sets; AGENT_TODO_HEADER_MARKERS, RAW_MATERIAL_KEY_HINTS, USER_TODO_HEADER_MARKERS 2 each), and the advisory is provably outside the committed inventory, so no budget or inventory count changes.
loopx-project#4614 merged while this branch was open and both append a dated row to the Appendix B decision log. Resolved by keeping both rows in date order, in both mirrors: 2026-09-16 for the B1 rename-invariance advisory, 2026-09-17 for the invariant domains this branch states. Neither row amends the other; the log is append-only by design. Validated after the merge: docs-governance-smoke ok, and tests/architecture/test_semantic_vocabulary_drift.py passes 93 tests. Signed-off-by: song <22676124+songoow@users.noreply.github.com>
…-visibility loopx-project#4614 merged while this branch was open. Both append a section to the same --report block: loopx-project#4614 prints the name-keyed fork advisory, this branch prints the merge candidates. Resolved additively, fork section first, because the two answer different questions -- a fork is one name whose modules disagree, a candidate group is two or more names carrying identical values, so neither report can substitute for the other. Verified after the merge: --report prints both sections, and the candidate header reads '20 to review, 18 explained by a registered vocabulary's own owner symbols, 38 raw groups'. Signed-off-by: song <22676124+songoow@users.noreply.github.com>
…ted one PR loopx-project#4643 retired the AGENT/USER_TODO_HEADER_MARKERS forks, which four drift tests from loopx-project#4614 had rented as samples for the rename-laundering limit. Track A retires real forks one by one, so renting them makes every retirement a test breakage -- this PR included. The pins now inject their own synthetic fork (one name, two modules, two disagreeing value sets) and measure against the live baseline instead of hardcoded counts, so they pin the machinery -- the RFC Section 9 known limit -- independently of the debt population: - the laundering test measures base / base+1 / base instead of 3/2; - the declaration test proves exclusion by removing SOURCE_SURFACES from the registry and watching the count rise by exactly one, instead of asserting today's undeclared count; - the three-case rename boundary runs its undeclared cases on the synthetic fork; Case 1 (declared rename refused) still uses SOURCE_SURFACES, which is still declared and still 4-way divergent; - the advisory test keeps one real assertion: RAW_MATERIAL_KEY_HINTS, the only undeclared fork surviving this PR, must stay listed, with a comment that the assertion retires with the fork. Signed-off-by: song <22676124+songoow@users.noreply.github.com>
Refs #4447 (Track B · B1 rename invariance, and the M0.5a scope slice it depends on).
Problem
Collisions are keyed by name. Renaming one side of a multi-value fork removes the name from
multi_value_forks, so the semantic fork budget falls while the drift stays in the tree. The RFC records this as a known limit (Section 9, "Renames launder a collision") and names the advisory merge report as the review aid.Measured on the baseline, that aid does not cover the case.
merge_candidate_groupsgroups different names carrying identical value sets; a fork is one name whose modules disagree, so the grouping lists no fork at all:Change
divergent_value_sets(inventory): advisory keyed by name, listing the forks that exist with how many value sets each carries. Printed bygenerate_semantic_inventory.py --reportin a newvalue_setssection.Correction: what this advisory does not do
The first version of this PR claimed the advisory catches a one-sided rename. Measurement disproved that, and the code docstring, the RFC mirrors, the Appendix B decision row and this description now all say what actually happens:
SOURCE_SURFACES)scope_declarationsnames every defining module and the renamed side no longer matchesSo the advisory's real value is narrower and stated as such: it lists surviving forks by name with their disagreement count, where before they were visible only as a number. It is not a rename detector, and the RFC limit stands unclosed. That residue is accepted for M0 and recorded as a decision rather than an omission.
Verification
Mutation-checked: forcing
divergent_value_setsto return[]fails the advisory test; the laundering test passes on the unmutated tree and fails if the limit is ever fixed without updating the RFC. No budget or inventory count changes — the advisory is not a budget input.🤖 Generated with Claude Code