Skip to content

feat(semantics): report the name-keyed value-set divergence a rename hides - #4614

Merged
huangruiteng merged 6 commits into
loopx-project:mainfrom
songoow:codex/b1-rename-invariance-evidence
Sep 17, 2026
Merged

huangruiteng merged 6 commits into
loopx-project:mainfrom
songoow:codex/b1-rename-invariance-evidence

Conversation

@songoow

@songoow songoow commented Sep 17, 2026 •

Copy link
Copy Markdown
Collaborator

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_groups groups different names carrying identical value sets; a fork is one name whose modules disagree, so the grouping lists no fork at all:

AGENT_TODO_HEADER_MARKERS -> appears in merge_candidate_groups? False
RAW_MATERIAL_KEY_HINTS    -> appears in merge_candidate_groups? False
USER_TODO_HEADER_MARKERS  -> appears in merge_candidate_groups? False

Change

  • divergent_value_sets(inventory): advisory keyed by name, listing the forks that exist with how many value sets each carries. Printed by generate_semantic_inventory.py --report in a new value_sets section.
  • Three tests pinning the complete boundary (see below).
  • RFC Section 9 known-limits entry corrected in both mirrors.

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:

Rename Budget Reported?
Declared name, one side (SOURCE_SURFACES) unchanged rejected — scope_declarations names every defining module and the renamed side no longer matches
Undeclared name, one side falls nothing reports it — the name is left with a single definition, so it stops being a fork and drops out of the advisory too
Undeclared name, every side falls nothing reports it — indistinguishable from an honest rename

So 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

semantic-vocabulary-drift-smoke: ok
  multi_value_forks=4/4 multi_value_forks_semantic=3/3 (unchanged)
docs-governance-smoke ok
loopx canary premerge --from-git-diff: ok, 0 failures

Mutation-checked: forcing divergent_value_sets to 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

…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>
@songoow

songoow commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator Author

自审(exact head cc90ff29b)

动机

RFC 第 9 节把"改名洗白冲突"记为已知限制,并指明缓解手段是 advisory 合并报告。本 PR 的动机是实测该缓解手段是否覆盖它所声称的案例——答案是不覆盖,因此这条已知限制的描述与实际防护之间有缺口,而缺口本身是静默的。

改动思路

冲突规则按名字归组。merge_candidate_groups 归组的是不同名字 + 值集完全相同;而分叉是同一个名字下模块互相分歧。两者集合不相交,所以分叉永远不会出现在该报告里。

实测(基线):

AGENT_TODO_HEADER_MARKERS / USER_TODO_HEADER_MARKERS / RAW_MATERIAL_KEY_HINTS
  -> 全部不在 merge_candidate_groups 中
改名 AGENT_TODO_HEADER_MARKERS 的一侧(state_projection.py):
  semantic undeclared 3 -> 2;multi_value_forks 4 -> 3

因此新增 divergent_value_sets(inventory):按名字归组的同名补充报告,由 --report 打印。它抓住单侧改名,因为残余定义在被我方遗弃的名字下仍然分歧。所有一侧同时改名与诚实改名无法区分(无快照、无名字账本),这一残余按 RFC 一贯做法记为 accepted,而不是假装修复。

具体改动

5 文件 +103/−2,纯增量。

  • loopx/semantics/inventory.py +28:divergent_value_sets,advisory,非预算输入。
  • scripts/generate_semantic_inventory.py +6:--report 增加 value_sets 段。
  • tests/architecture/test_semantic_vocabulary_drift.py +53:两条测试。
  • 两份 RFC 镜像 +9/−1、+7/−1:第 9 节边界如实改写。

对主干的风险

预算与清单计数零变化(multi_value_forks=4/4、multi_value_forks_semantic=3/3),因为 advisory 不是预算输入——这是刻意的:审计输出若进入预算就成了第二权威,正是 RFC 禁止的。

第二条测试断言 divergent_value_sets 不出现在已提交 inventory 中,把"advisory 不得成为权威"钉住。

关于第一条测试的定性:它断言改名仍然降低预算(3 → 2),即断言限制按文档存在,而非断言它应当如此。docstring 写明:若将来有人修好这个限制而没有同步 RFC,该测试必须失败。这是哨兵,不是背书。

变异验证:强制 divergent_value_sets 返回 [] → 第二条测试失败;控制组(无变异)通过。

我的整体评价

observable_semantics:基线 vs head 的决策语义完全相等——本 PR 只增加一个不参与判定的审查信号,并纠正一处与实际防护不符的文档描述。未新增词表、未扩宽既有词表,属于已有 scope/inventory 机制的复用。

验证:drift smoke ok、docs-governance ok、loopx canary premerge --from-git-diff ok(0 失败),且已在合并最新 main 后重跑(Python Tests 在 main 上已恢复 success)。

Residual risk: 全侧改名仍不可测,已在 RFC 明写;merge_candidate_groups 与 divergent_value_sets 目前均无测试消费者,本 PR 只补了后者。

Verdict: APPROVE (self-review) — do not self-merge (control-plane change, per #4586).

songoow and others added 2 commits September 16, 2026 21:32
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>
@songoow

songoow commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator Author

exact-head 复核(a0826c93e)— 更正我自己上一轮的结论

更正内容

本 PR 上一版(c973bd735 及此前)声称 divergent_value_sets 能抓住单侧改名。实测证否,这一点必须撤回:

我把"改名 AGENT_TODO_HEADER_MARKERS 后报告里仍出现 USER_TODO_HEADER_MARKERS"错读成了结果——那其实是另一个未被改名的名字。改名后该名字只剩一份定义,因此不再构成分叉,会同时退出预算与这份报告。

我已按实情更正:代码 docstring、两份 RFC 镜像第 9 节、附录 B 决策行、以及 PR 描述。

实测的完整边界(三条测试已钉住)

改名情形 预算 是否有报告
已声明名字,单侧(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>
@songoow

songoow commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator Author

exact-head 复核(f64d30c4b)— 同步 main

之前 BEHIND main 15 个提交,干净合并,无冲突。

顺带说明一点本地读数问题:合并前这个分支的 conflicting_values_semantic 仍是 2,而 main 上 #4603 已降到 0。因为本分支未改动该行,git 取 main 侧,不会把预算放松回去——但在合并前于本分支上跑 smoke,看到的是旧预算。现在两边一致为 0。

本 PR 的实质内容与自审见上一条评论,未变。合并提交带 DCO 签名。

…iance-evidence

Signed-off-by: song <22676124+songoow@users.noreply.github.com>
@songoow

songoow commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator Author

本 PR 在 #4447 计划中的位置

issue #4447 现在有一节统一协调(中英双语),把这 13 个在开 PR 作为一个计划列出:各自修什么、为何必要、以及实测出的合并顺序。

冲突实测:对全部 78 对做了试合并,9 对冲突,分四簇,每一处都是文本相邻,没有一处是语义分歧。

冲突簇 涉及 PR 后合并者的解法
棘轮锚点 #4606、#4608、#4617、#4629 四者全部合并后的终态已实测:same_runtime_forks 18、same_runtime_fork_definitions 41、same_runtime_forks_semantic 11、conflicting_values 16、conflicting_definitions 55、multi_value_twins 13。registry 与 BUDGET_ANCHOR 两处都要带同一组值(该检查是相等而非 <=)
漂移测试文件 #4626、#4629、#4631 三者都在文件末尾追加,全部保留即可
RFC 双镜像 #4614、#4627、#4631 附录 B 决策行,按日期先后保留两条
清单与生成器 #4614、#4630 加法式:owner 对过滤与改名不变性证据同时保留

建议顺序(代价从低到高):#4628 → #4625、#4626 → #4627 → #4619、#4621 → #4630 → #4614 → #4631 → #4629 → #4617 → #4606 → #4608。四个棘轮 PR 放最后,因为每落地一个,下一个的数字就从估算变成确定值。

全部 13 个 PR 现已同步到 main、零失败检查。

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

详细评审 — 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.

@huangruiteng
huangruiteng merged commit 0a49179 into loopx-project:main Sep 17, 2026
27 checks passed
songoow added a commit to songoow/loopx that referenced this pull request Sep 17, 2026
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>
songoow added a commit to songoow/loopx that referenced this pull request Sep 17, 2026
…-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>
songoow added a commit to songoow/loopx that referenced this pull request Sep 17, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants