From 188e64e41b22aaa7e8320e9b226835ce48935d14 Mon Sep 17 00:00:00 2001 From: song <22676124+songoow@users.noreply.github.com> Date: Fri, 18 Sep 2026 11:48:28 -0400 Subject: [PATCH] docs(rfcs): record what the decision log can verify, not a status frozen at authoring Every one of the ten dated rows in Appendix B carried an authorization status from the moment the row was written, while all ten changes were already on `main`. Two rows said "kernel-maintainer approval required, not yet given" for work that had landed; one of those, the F1/F2 domain binding, has an explicit approving review on record. A reader of `main` would conclude the current `goal_boundary` migration budget of 16 is unauthorized. Nothing updated the column at merge, which is the actual defect. Correcting ten rows without changing that would only reset a clock that drifts again, so the appendix now states what the column records and when it is updated: - The column records acts verifiable from the repository -- the merge commit, and the approving review where one exists -- not a status at authoring time. - A row is updated in the PR that lands the change, in the same diff. - A merge and an approving review are different acts and are named differently. That last rule is why #4651's two rows do not say "approved". It was merged by the maintainer in `02cc53bd5` while a CHANGES_REQUESTED review still stood, and no approving review exists. Whether a maintainer merge satisfies Section 5 for a budget raise is the maintainer's call; the row records the act and says so, rather than resolving it here. This table has now been wrong in both directions. An earlier revision of one of these rows asserted an approval that did not exist, sourced from an instruction given outside the repository; review caught it and it was withdrawn. Leaving "not yet given" standing after the change landed is the same failure mirrored -- both mislead a reader who cannot see what was said elsewhere, and both come from the column describing a conversation instead of the tree. Limit, stated rather than left implied: this is a prose contract with no check behind it. Verifying "no row claims pending while its PR is merged" needs GitHub state, which the smoke deliberately cannot reach, and adding a scanner for it would extend the apparatus this tracker just scoped down. The bilingual mirrors and the dated row sequence are checked; the authorization column is not. Verified: docs-governance-smoke ok; semantic-vocabulary-drift-smoke ok; tests/architecture 619 passed; canary premerge from-git-diff 0 failures; both mirrors carry 10 rows with an identical date sequence. Refs #4447 Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: song <22676124+songoow@users.noreply.github.com> --- .../semantic-vocabulary-convergence-v0.md | 38 ++++++++++++++----- ...emantic-vocabulary-convergence-v0.zh-CN.md | 33 +++++++++++----- 2 files changed, 51 insertions(+), 20 deletions(-) diff --git a/docs/architecture/rfcs/semantic-vocabulary-convergence-v0.md b/docs/architecture/rfcs/semantic-vocabulary-convergence-v0.md index 5b00686cc5..54aaee1764 100644 --- a/docs/architecture/rfcs/semantic-vocabulary-convergence-v0.md +++ b/docs/architecture/rfcs/semantic-vocabulary-convergence-v0.md @@ -1855,18 +1855,36 @@ result on the current tree; what changes is what the invariants claim. ## Appendix B: Decision log +**What the "Owner / approval" column records, and when it is updated.** The +column records **acts that can be verified from the repository** -- the merge +commit, and the approving review where one exists -- not a status at the time the +row was written. Two rules follow, both learned from this table going wrong in +both directions: + +1. **A row is updated in the PR that lands the change**, in the same diff. Every + row here once said "PR review pending" or "approval not yet given" while its + change was already on `main`; nothing updated the column at merge, so the + ledger stated the opposite of the tree. A row that freezes at authoring time is + worse than no row, because a reader treats a closed-looking record as settled. +2. **A merge and an approving review are different acts and are named + differently.** Where Section 5 requires kernel-maintainer approval, record + which one actually happened. Do not write "approved" from an instruction given + outside the repository, and do not leave "not yet given" standing after the + change has landed -- both have happened on this table, and each misleads a + reader who cannot see what was said elsewhere. + | Date | Decision | Owner / approval | Alternatives | Normative sections changed | | --- | --- | --- | --- | --- | -| 2026-09-16 | Q9: compute the full inventory on demand; retire the committed census | Implementation for [maintainer feedback](https://github.com/huangruiteng/loopx/pull/4360#issuecomment-5692062394); PR review pending | Committed snapshot with post-merge regeneration; diff-only scan rejected | 1, I6, 3, 5, 9, 10, 12 | -| 2026-09-16 | B2: bind one unrenamed re-export hop in the Python producer scanner | Implementation, Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) B2; PR review pending | Require every consumer to import the owner module (fragile; failed silently in M2); unbounded multi-hop resolution rejected | 5, Appendix A | -| 2026-09-17 | B3 repair: count all five orthogonal use facts rather than the role labels, and put field-specific unknowns inside the migration surface | Implementation, Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) B3; raises `goal_boundary` 15 to 16, `work_lane_contract` 28 to 29 and `external_evidence_observation` 6 to 7, each being one module that carries the field name as data, with token budgets and carrier counts unmoved; per Section 5 **kernel-maintainer approval required, not yet given** | Keep counting the role labels and add an overlap column (rejected: the label is single-valued, so any count built from it under-reports whichever fact sorted second, and one more column would not change that); leave `unresolved` outside the surface as "not known to need migration" (rejected: it is known to need investigation, and a budget that excludes it falls when a reader is rewritten into a form the scan cannot resolve); raise on an unparseable module (rejected: it fails the scan for a direct caller working on a half-written tree, and unknown is the honest classification, not a louder one); rely on the cross-runtime equivalence test alone (rejected: it asserts agreement, and two runtimes that both read a destructuring as prose agree) | 11, Appendix A, Appendix B | -| 2026-09-17 | B3: budget the migration surface beside the token count; keep both until Q11 | Implementation, Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) B3; PR review pending | Replacing the token budget outright (rejected: the token count is the anchor that proves the new roles partition the same population, and dropping it in the same diff that introduces them would make the smaller number unauditable); counting Python computed-key subscripts as unresolved reads (rejected: `rows[index]` and `payload[key]` are one syntax, and the unknown would stop carrying information; TypeScript has no mapping-accessor convention, so its computed member access is counted separately and stated as an upper bound) | 5, 9, 11, 12 | -| 2026-09-16 | B1 rename invariance: add the name-keyed divergence advisory; state the limit it does not close | Implementation, Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) B1; PR review pending | Keying the budget on value sets (rejected: `CONFIDENCE_LEVELS` and `EDGE_CASE_COMPLEXITIES` share `high/low/medium` with different meanings); a committed name ledger (rejected at M0: Q9 retired the committed census). The advisory lists surviving forks by name; it was first described as catching a one-sided rename, which measurement disproved, so both mirrors state the limit as it behaves | 9 | -| 2026-09-17 | B2: bind same-module call results, ordered local rebinding and key-precise container writes; reclassify the TypeScript residue rather than shrink it | Implementation, Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) B2; PR review pending | Bind cross-module calls and object fields (rejected: a separate bounded form with its own blast radius, not this slice); count a field-named keyword as production (rejected: it makes the obligation tautological, Section 5); leave `typescript_dynamic` as one catch-all (rejected: eight sites shared one reason, so the residue was not actionable); bind the callee's parameters to the call-site arguments (rejected: the answer would depend on the caller and could not be memoised, and a wrong binding would invent evidence) | 5, 9, Appendix A | -| 2026-09-17 | B5 (optional): report consumer roles per site only for vocabularies that declare `literal_scan.field`, and state the unknown share instead of classifying everything | Implementation, Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) B5; PR review pending | Fall back to the vocabulary id when no slot is declared (rejected on review: it analysed 25 of 26 vocabularies against a field name nobody declared, and every row downstream inherited the guess; they are now named under `missing_slot_identity` and not analysed); register consumers in the registry (rejected: the tracking issue forbids blanket consumer registration, and a declared list is a claim rather than evidence); make the report a merge gate (rejected: F3 is the advisory lane, and a 90.2% unknown share cannot gate anything); report only the sites the grammar resolves (rejected: the tables would read as complete, so an unrecognized mention and a computed-key read are printed rows with reasons) | 9, Appendix A, Appendix B, Appendix C | -| 2026-09-17 | B0: state schema validation, implementation stage, evidence status and blocking behaviour separately for I2/I11-I14 and the enforcement lanes; require each formal invariant id exactly once | Implementation, Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) B0; PR review pending | Rename the `blocking_next` lane to match its behaviour (rejected: the lane name is the milestone that owns the check, and renaming it would lose that and collapse the two readings the other way); add a `blocks_today` boolean to `formal_model` (rejected: it would be one more declared field a reader could mistake for a measurement, and the fact is a property of the smoke's `main()`, which no registry edit can change); leave the lane gloss and note the gap in the ledger only (rejected: the gloss is the sentence a reviewer quotes) | 2, 5, 11, Appendix A, Appendix B | -| 2026-09-17 | Bound F1/F2 to the kernel tier and the scan reach, restate F4 as scope enumeration completeness, and give every obligation a derived `domain` | Implementation, Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447); **kernel-maintainer approval required, not yet given** | Leave the unconditional statements and record the gap in prose only (rejected: the statement was stronger than `validate_production`'s own docstring); restate F4 as per-context value-set disjointness (rejected: refuted by the repo's own data, since `scope_declarations` exists to permit legitimate same-name reuse); widen the scan so the unconditional claim becomes true (rejected: a separate change with its own risk) | 5, 9, Appendix B, Appendix C | -| 2026-09-17 | Document every `cross_runtime` value with the condition that produces it, taking per-value coverage from 68/149 to 149/149, and ratchet it in a separate test file | Implementation, Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) Track A; PR review pending | Append to the kernel ratchet at the end of `test_semantic_vocabulary_drift.py` (rejected: three open PRs already collide on that tail, and a same-diff rule is exactly what a merge there loses); infer a meaning for the two values with no producer (rejected by the evidence rules: a guessed note is indistinguishable from a verified one once it is in the table); document only the values a branch selects (rejected: it would leave the author-declared values looking undocumented rather than declared, which is the more useful fact); enforce the "not a restatement" bar with a character floor plus a non-stopword word count, and cap the unresolved count at 2 (both rejected under review: a word count cannot show that a note names the producing condition and only rewards padding, and a budget on honesty pressures the next author to invent a condition rather than record missing evidence) | Appendix A, Appendix B | +| 2026-09-16 | Q9: compute the full inventory on demand; retire the committed census | Implementation for [maintainer feedback](https://github.com/huangruiteng/loopx/pull/4360#issuecomment-5692062394); landed in [#4494](https://github.com/huangruiteng/loopx/pull/4494), merge `75fcd5556` | Committed snapshot with post-merge regeneration; diff-only scan rejected | 1, I6, 3, 5, 9, 10, 12 | +| 2026-09-16 | B2: bind one unrenamed re-export hop in the Python producer scanner | Implementation, Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) B2; landed in [#4573](https://github.com/huangruiteng/loopx/pull/4573), merge `6979d528b`, approved by @huangruiteng | Require every consumer to import the owner module (fragile; failed silently in M2); unbounded multi-hop resolution rejected | 5, Appendix A | +| 2026-09-17 | B3 repair: count all five orthogonal use facts rather than the role labels, and put field-specific unknowns inside the migration surface | Implementation, Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) B3; raises `goal_boundary` 15 to 16, `work_lane_contract` 28 to 29 and `external_evidence_observation` 6 to 7, each being one module that carries the field name as data, with token budgets and carrier counts unmoved; per Section 5 this needs kernel-maintainer approval. **What is on record is a merge, not an approving review:** [#4651](https://github.com/huangruiteng/loopx/pull/4651) was merged by @huangruiteng in `02cc53bd5` on 2026-09-18 while a `CHANGES_REQUESTED` review still stood, and no approving review exists. Whether a maintainer merge satisfies Section 5 for a budget raise is the maintainer's call; this row records the act, not a conclusion about it | Keep counting the role labels and add an overlap column (rejected: the label is single-valued, so any count built from it under-reports whichever fact sorted second, and one more column would not change that); leave `unresolved` outside the surface as "not known to need migration" (rejected: it is known to need investigation, and a budget that excludes it falls when a reader is rewritten into a form the scan cannot resolve); raise on an unparseable module (rejected: it fails the scan for a direct caller working on a half-written tree, and unknown is the honest classification, not a louder one); rely on the cross-runtime equivalence test alone (rejected: it asserts agreement, and two runtimes that both read a destructuring as prose agree) | 11, Appendix A, Appendix B | +| 2026-09-17 | B3: budget the migration surface beside the token count; keep both until Q11 | Implementation, Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) B3; landed in [#4651](https://github.com/huangruiteng/loopx/pull/4651), merge `02cc53bd5` | Replacing the token budget outright (rejected: the token count is the anchor that proves the new roles partition the same population, and dropping it in the same diff that introduces them would make the smaller number unauditable); counting Python computed-key subscripts as unresolved reads (rejected: `rows[index]` and `payload[key]` are one syntax, and the unknown would stop carrying information; TypeScript has no mapping-accessor convention, so its computed member access is counted separately and stated as an upper bound) | 5, 9, 11, 12 | +| 2026-09-16 | B1 rename invariance: add the name-keyed divergence advisory; state the limit it does not close | Implementation, Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) B1; landed in [#4614](https://github.com/huangruiteng/loopx/pull/4614), merge `0a4917956`, approved by @huangruiteng | Keying the budget on value sets (rejected: `CONFIDENCE_LEVELS` and `EDGE_CASE_COMPLEXITIES` share `high/low/medium` with different meanings); a committed name ledger (rejected at M0: Q9 retired the committed census). The advisory lists surviving forks by name; it was first described as catching a one-sided rename, which measurement disproved, so both mirrors state the limit as it behaves | 9 | +| 2026-09-17 | B2: bind same-module call results, ordered local rebinding and key-precise container writes; reclassify the TypeScript residue rather than shrink it | Implementation, Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) B2; landed in [#4682](https://github.com/huangruiteng/loopx/pull/4682), merge `14a766e50`, approved by @huangruiteng | Bind cross-module calls and object fields (rejected: a separate bounded form with its own blast radius, not this slice); count a field-named keyword as production (rejected: it makes the obligation tautological, Section 5); leave `typescript_dynamic` as one catch-all (rejected: eight sites shared one reason, so the residue was not actionable); bind the callee's parameters to the call-site arguments (rejected: the answer would depend on the caller and could not be memoised, and a wrong binding would invent evidence) | 5, 9, Appendix A | +| 2026-09-17 | B5 (optional): report consumer roles per site only for vocabularies that declare `literal_scan.field`, and state the unknown share instead of classifying everything | Implementation, Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) B5; landed in [#4663](https://github.com/huangruiteng/loopx/pull/4663), merge `736299027`, approved by @huangruiteng | Fall back to the vocabulary id when no slot is declared (rejected on review: it analysed 25 of 26 vocabularies against a field name nobody declared, and every row downstream inherited the guess; they are now named under `missing_slot_identity` and not analysed); register consumers in the registry (rejected: the tracking issue forbids blanket consumer registration, and a declared list is a claim rather than evidence); make the report a merge gate (rejected: F3 is the advisory lane, and a 90.2% unknown share cannot gate anything); report only the sites the grammar resolves (rejected: the tables would read as complete, so an unrecognized mention and a computed-key read are printed rows with reasons) | 9, Appendix A, Appendix B, Appendix C | +| 2026-09-17 | B0: state schema validation, implementation stage, evidence status and blocking behaviour separately for I2/I11-I14 and the enforcement lanes; require each formal invariant id exactly once | Implementation, Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) B0; landed in [#4661](https://github.com/huangruiteng/loopx/pull/4661), merge `2303ff033`, approved by @huangruiteng | Rename the `blocking_next` lane to match its behaviour (rejected: the lane name is the milestone that owns the check, and renaming it would lose that and collapse the two readings the other way); add a `blocks_today` boolean to `formal_model` (rejected: it would be one more declared field a reader could mistake for a measurement, and the fact is a property of the smoke's `main()`, which no registry edit can change); leave the lane gloss and note the gap in the ledger only (rejected: the gloss is the sentence a reviewer quotes) | 2, 5, 11, Appendix A, Appendix B | +| 2026-09-17 | Bound F1/F2 to the kernel tier and the scan reach, restate F4 as scope enumeration completeness, and give every obligation a derived `domain` | Implementation, Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447); **approved** by @huangruiteng on [#4631](https://github.com/huangruiteng/loopx/pull/4631), merge `440b002fb` (2026-09-17); the approving review is the recorded authorization | Leave the unconditional statements and record the gap in prose only (rejected: the statement was stronger than `validate_production`'s own docstring); restate F4 as per-context value-set disjointness (rejected: refuted by the repo's own data, since `scope_declarations` exists to permit legitimate same-name reuse); widen the scan so the unconditional claim becomes true (rejected: a separate change with its own risk) | 5, 9, Appendix B, Appendix C | +| 2026-09-17 | Document every `cross_runtime` value with the condition that produces it, taking per-value coverage from 68/149 to 149/149, and ratchet it in a separate test file | Implementation, Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) Track A; landed in [#4662](https://github.com/huangruiteng/loopx/pull/4662), merge `11b857dec`, approved by @huangruiteng | Append to the kernel ratchet at the end of `test_semantic_vocabulary_drift.py` (rejected: three open PRs already collide on that tail, and a same-diff rule is exactly what a merge there loses); infer a meaning for the two values with no producer (rejected by the evidence rules: a guessed note is indistinguishable from a verified one once it is in the table); document only the values a branch selects (rejected: it would leave the author-declared values looking undocumented rather than declared, which is the more useful fact); enforce the "not a restatement" bar with a character floor plus a non-stopword word count, and cap the unresolved count at 2 (both rejected under review: a word count cannot show that a note names the producing condition and only rewards padding, and a budget on honesty pressures the next author to invent a condition rather than record missing evidence) | Appendix A, Appendix B | ## Appendix C: Evidence registry diff --git a/docs/architecture/rfcs/semantic-vocabulary-convergence-v0.zh-CN.md b/docs/architecture/rfcs/semantic-vocabulary-convergence-v0.zh-CN.md index 47b25e14f0..4cdf94f39f 100644 --- a/docs/architecture/rfcs/semantic-vocabulary-convergence-v0.zh-CN.md +++ b/docs/architecture/rfcs/semantic-vocabulary-convergence-v0.zh-CN.md @@ -1466,18 +1466,31 @@ Python 的 `dict_literal_key` 同样计为写入,类型/接口属性签名 ## 附录 B:决策日志 +**「负责角色 / 批准」这一列记录什么,何时更新。** 这一列记录的是**可以从仓库本身核实的行为** +—— 合并提交,以及存在批准评审时的那条评审 —— 而不是写下该行时的状态。由此有两条规则, +两条都来自这张表在两个方向上各错过一次: + +1. **谁让改动落地,谁在同一个 diff 里更新对应行。** 这张表的每一行都曾写着「PR 评审待完成」 + 或「批准尚未给出」,而对应改动已经在 `main` 上;合并时没有任何东西更新这一列,于是账本 + 陈述了与树相反的事实。一行停在撰写时刻比没有这一行更糟,因为读者会把看起来已闭合的记录 + 当作已定。 +2. **合并与批准评审是两种行为,必须分别命名。** 凡第 5 节要求内核维护者批准之处,记录**实际 + 发生的那一种**。不要根据仓库之外给出的指示写「已批准」,也不要在改动落地后还留着「尚未 + 给出」—— 这两种错误都在这张表上发生过,而且都会误导一个看不到仓库外对话的读者。 + + | 日期 | 决策 | Owner / 批准 | 备选 | 变更的规范章节 | | --- | --- | --- | --- | --- | -| 2026-09-16 | Q9:全树按需计算;移除已提交结构清单 | 根据[维护者反馈](https://github.com/huangruiteng/loopx/pull/4360#issuecomment-5692062394)实现,PR 评审待完成 | 取代合并后补再生成;拒绝只扫描 diff | 1、I6、3、5、9、10、12 | -| 2026-09-16 | B2:Python producer 扫描器绑定一跳未改名再导出 | 实现,Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) B2;PR 评审待完成 | 要求每个消费者都从 owner 模块导入(脆弱;M2 中已静默失效);拒绝无界多跳解析 | 5、附录 A | -| 2026-09-17 | B3 修复:统计全部五项相互正交的使用事实而不是角色标签,并把字段专属的未知纳入迁移面 | 实现,Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) B3;将 `goal_boundary` 由 15 上调至 16、`work_lane_contract` 由 28 上调至 29、`external_evidence_observation` 由 6 上调至 7,三处上调各自只涉及一个把字段名当数据携带的模块,token 预算与 carrier 计数均未变动;按第 5 节**需要内核维护者批准,尚未给出** | 继续统计角色标签并加一列重叠数(否决:该标签单值,任何由它构造的计数都会漏报排序靠后的那个事实,多加一列并不改变这一点);让 `unresolved` 留在迁移面之外,理由是「并不已知需要迁移」(否决:它是已知需要调查的,而把它排除在外的预算会在有人把读者改写成扫描无法解析的形式时下降);对不可解析模块直接抛错(否决:这会让在半写状态树上工作的直接调用方整个扫描失败,而「未知」是诚实的分类,不是更响的那一种);只依赖跨运行时等价性测试(否决:它断言的是一致,而两个运行时都把解构读成散文也是一致的) | 11、附录 A、附录 B | -| 2026-09-17 | B3:在 token 计数旁边为迁移面设预算;Q11 决策前两者都保留 | 实现,Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) B3;PR 评审待完成 | 直接用新指标取代 token 预算(否决:token 计数正是证明新角色划分同一批模块的锚,在引入角色的同一个 diff 里把它删掉会让更小的数字无法复核);把 Python 计算式下标也计为未定读取(否决:`rows[index]` 与 `payload[key]` 是同一种语法,未知数会大到不再携带信息;TypeScript 没有 mapping 访问器约定,其计算式成员访问单独计数并声明为上界) | 5、9、11、12 | -| 2026-09-16 | B1 改名不变性:新增按名字归组的分歧报告;写明它未闭合的边界 | 实现,Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) B1;PR 评审待完成 | 把预算改按值集归组(否决:`CONFIDENCE_LEVELS` 与 `EDGE_CASE_COMPLEXITIES` 共享 `high/low/medium` 而含义不同);提交名字账本(M0 否决:Q9 已退役提交式清单)。该报告列出仍然存在的分叉;初稿称它能抓住单侧改名,实测证否,故两份镜像按真实行为写明边界 | 9 | -| 2026-09-17 | B2:绑定同模块调用结果、局部变量有序重绑定与按键精确的容器写入;对 TypeScript 残量做重新归类而非缩减 | 实现,Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) B2;PR 评审待完成 | 绑定跨模块调用与对象字段(拒绝:那是另一条有自己影响面的有界形式,不属于本切片);把与字段同名的关键字算作生产(拒绝:会使该义务变成同义反复,见第 5 节);保留 `typescript_dynamic` 作为单一兜底(拒绝:八个位点共用一个原因,残量无法被行动);把被调方形参绑定到调用点实参(拒绝:结果会依赖调用方而无法记忆化,且一次错误绑定会凭空造出证据) | 5、9、附录 A | -| 2026-09-17 | B5(可选项):只对声明了 `literal_scan.field` 的词表按位点报告消费者角色,并写明未知占比,而不是把一切都分类 | 实现,Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) B5;PR 评审待完成 | 没有声明槽位时回退到词表 id(评审中否决:这把 26 个词表中的 25 个按一个谁也没声明过的字段名去分析,下游每一行都继承了该猜测;它们现在列在 `missing_slot_identity` 下,不做分析);在注册表中登记消费者(否决:跟踪 issue 明令禁止全量消费者登记,且一份声明清单是主张而非证据);把该报告做成合并闸门(否决:F3 属于参考层级,90.2% 的未知占比也不足以闸住任何东西);只报告语法能解析的位点(否决:这会让表格读起来像是完整的,因此未识别的提及与计算键读取都作为带原因的行打印出来) | 9、附录 A、附录 B、附录 C | -| 2026-09-17 | B0:为 I2/I11-I14 与各强制层级分别陈述 schema 校验、实施阶段、证据状态与阻断行为;要求每个形式不变量 ID 恰好出现一次 | 实现,Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) B0;PR 评审待完成 | 把 `blocking_next` 层级改名以匹配其行为(否决:层级名字表示拥有该检查的里程碑,改名会丢掉这层含义,并从另一个方向把两种读法重新合并);在 `formal_model` 中加一个 `blocks_today` 布尔字段(否决:那只会多出一个可被读者误当作度量的声明字段,而该事实是 smoke `main()` 的性质,任何注册表修改都改不了它);保留原注解、只在账本里记一笔缺口(否决:评审者引用的正是那句注解) | 2、5、11、附录 A、附录 B | -| 2026-09-17 | 将 F1/F2 限定在 kernel 层与扫描范围,把 F4 重述为作用域枚举完备性,并给每条义务加上可推导的 `domain` | 实现,Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447);**需要内核维护者批准,尚未获得** | 保留无条件表述、只在正文记一笔缺口(否决:该表述比 `validate_production` 自己的 docstring 还强);把 F4 重述为各上下文值集互斥(否决:会被仓库自身数据推翻,`scope_declarations` 恰恰就是为了允许合理的同名复用);扒宽扫描让无条件声明成立(否决:那是自带风险的另一个变更) | 5、9、附录 B、附录 C | -| 2026-09-17 | 为每个 `cross_runtime` 值写明产生它的条件,把逐值覆盖率从 68/149 提到 149/149,并用一个独立测试文件加以棘轮化 | 实现,Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) Track A;PR 评审待完成 | 追加到 `test_semantic_vocabulary_drift.py` 末尾的 kernel 棘轮(否决:已有三个未合 PR 在该处冲突,而同一 diff 内的规则正是合并时最容易丢失的东西);为两个没有生产者的值推断含义(按证据规则否决:一旦写进表里,臆测的备注与经核实的备注无法区分);只记录由分支选择的值(否决:那会让由作者声明的值看起来像是没写,而“由作者声明”本身才是更有用的事实);用字符下限加非停用词计数来强制“不得只是复述”这条标准,并把未解析数量上限钉在 2(评审中双双否决:词数无法说明一条备注写出了产生条件,只会奖励灌水;而给诚实设预算会逼迫下一位作者编造条件,而不是如实记下证据缺失) | 附录 A、附录 B | +| 2026-09-16 | Q9:全树按需计算;移除已提交结构清单 | 根据[维护者反馈](https://github.com/huangruiteng/loopx/pull/4360#issuecomment-5692062394)实现,已落地于 [#4494](https://github.com/huangruiteng/loopx/pull/4494),合并 `75fcd5556` | 取代合并后补再生成;拒绝只扫描 diff | 1、I6、3、5、9、10、12 | +| 2026-09-16 | B2:Python producer 扫描器绑定一跳未改名再导出 | 实现,Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) B2;已落地于 [#4573](https://github.com/huangruiteng/loopx/pull/4573),合并 `6979d528b`,@huangruiteng 已批准 | 要求每个消费者都从 owner 模块导入(脆弱;M2 中已静默失效);拒绝无界多跳解析 | 5、附录 A | +| 2026-09-17 | B3 修复:统计全部五项相互正交的使用事实而不是角色标签,并把字段专属的未知纳入迁移面 | 实现,Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) B3;将 `goal_boundary` 由 15 上调至 16、`work_lane_contract` 由 28 上调至 29、`external_evidence_observation` 由 6 上调至 7,三处上调各自只涉及一个把字段名当数据携带的模块,token 预算与 carrier 计数均未变动;按第 5 节这需要内核维护者批准。**在案的是一次合并,不是一条批准评审:**[#4651](https://github.com/huangruiteng/loopx/pull/4651) 由 @huangruiteng 于 2026-09-18 以 `02cc53bd5` 合并,当时一条 `CHANGES_REQUESTED` 评审仍然成立,且不存在任何批准评审。维护者合并是否满足第 5 节对预算上调的要求,由维护者判断;本行记录的是这个行为,不是对它的结论 | 继续统计角色标签并加一列重叠数(否决:该标签单值,任何由它构造的计数都会漏报排序靠后的那个事实,多加一列并不改变这一点);让 `unresolved` 留在迁移面之外,理由是「并不已知需要迁移」(否决:它是已知需要调查的,而把它排除在外的预算会在有人把读者改写成扫描无法解析的形式时下降);对不可解析模块直接抛错(否决:这会让在半写状态树上工作的直接调用方整个扫描失败,而「未知」是诚实的分类,不是更响的那一种);只依赖跨运行时等价性测试(否决:它断言的是一致,而两个运行时都把解构读成散文也是一致的) | 11、附录 A、附录 B | +| 2026-09-17 | B3:在 token 计数旁边为迁移面设预算;Q11 决策前两者都保留 | 实现,Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) B3;已落地于 [#4651](https://github.com/huangruiteng/loopx/pull/4651),合并 `02cc53bd5` | 直接用新指标取代 token 预算(否决:token 计数正是证明新角色划分同一批模块的锚,在引入角色的同一个 diff 里把它删掉会让更小的数字无法复核);把 Python 计算式下标也计为未定读取(否决:`rows[index]` 与 `payload[key]` 是同一种语法,未知数会大到不再携带信息;TypeScript 没有 mapping 访问器约定,其计算式成员访问单独计数并声明为上界) | 5、9、11、12 | +| 2026-09-16 | B1 改名不变性:新增按名字归组的分歧报告;写明它未闭合的边界 | 实现,Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) B1;已落地于 [#4614](https://github.com/huangruiteng/loopx/pull/4614),合并 `0a4917956`,@huangruiteng 已批准 | 把预算改按值集归组(否决:`CONFIDENCE_LEVELS` 与 `EDGE_CASE_COMPLEXITIES` 共享 `high/low/medium` 而含义不同);提交名字账本(M0 否决:Q9 已退役提交式清单)。该报告列出仍然存在的分叉;初稿称它能抓住单侧改名,实测证否,故两份镜像按真实行为写明边界 | 9 | +| 2026-09-17 | B2:绑定同模块调用结果、局部变量有序重绑定与按键精确的容器写入;对 TypeScript 残量做重新归类而非缩减 | 实现,Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) B2;已落地于 [#4682](https://github.com/huangruiteng/loopx/pull/4682),合并 `14a766e50`,@huangruiteng 已批准 | 绑定跨模块调用与对象字段(拒绝:那是另一条有自己影响面的有界形式,不属于本切片);把与字段同名的关键字算作生产(拒绝:会使该义务变成同义反复,见第 5 节);保留 `typescript_dynamic` 作为单一兜底(拒绝:八个位点共用一个原因,残量无法被行动);把被调方形参绑定到调用点实参(拒绝:结果会依赖调用方而无法记忆化,且一次错误绑定会凭空造出证据) | 5、9、附录 A | +| 2026-09-17 | B5(可选项):只对声明了 `literal_scan.field` 的词表按位点报告消费者角色,并写明未知占比,而不是把一切都分类 | 实现,Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) B5;已落地于 [#4663](https://github.com/huangruiteng/loopx/pull/4663),合并 `736299027`,@huangruiteng 已批准 | 没有声明槽位时回退到词表 id(评审中否决:这把 26 个词表中的 25 个按一个谁也没声明过的字段名去分析,下游每一行都继承了该猜测;它们现在列在 `missing_slot_identity` 下,不做分析);在注册表中登记消费者(否决:跟踪 issue 明令禁止全量消费者登记,且一份声明清单是主张而非证据);把该报告做成合并闸门(否决:F3 属于参考层级,90.2% 的未知占比也不足以闸住任何东西);只报告语法能解析的位点(否决:这会让表格读起来像是完整的,因此未识别的提及与计算键读取都作为带原因的行打印出来) | 9、附录 A、附录 B、附录 C | +| 2026-09-17 | B0:为 I2/I11-I14 与各强制层级分别陈述 schema 校验、实施阶段、证据状态与阻断行为;要求每个形式不变量 ID 恰好出现一次 | 实现,Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) B0;已落地于 [#4661](https://github.com/huangruiteng/loopx/pull/4661),合并 `2303ff033`,@huangruiteng 已批准 | 把 `blocking_next` 层级改名以匹配其行为(否决:层级名字表示拥有该检查的里程碑,改名会丢掉这层含义,并从另一个方向把两种读法重新合并);在 `formal_model` 中加一个 `blocks_today` 布尔字段(否决:那只会多出一个可被读者误当作度量的声明字段,而该事实是 smoke `main()` 的性质,任何注册表修改都改不了它);保留原注解、只在账本里记一笔缺口(否决:评审者引用的正是那句注解) | 2、5、11、附录 A、附录 B | +| 2026-09-17 | 将 F1/F2 限定在 kernel 层与扫描范围,把 F4 重述为作用域枚举完备性,并给每条义务加上可推导的 `domain` | 实现,Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447);**已获批准** —— @huangruiteng 在 [#4631](https://github.com/huangruiteng/loopx/pull/4631) 上给出,合并 `440b002fb`(2026-09-17);那条批准评审即是在案授权 | 保留无条件表述、只在正文记一笔缺口(否决:该表述比 `validate_production` 自己的 docstring 还强);把 F4 重述为各上下文值集互斥(否决:会被仓库自身数据推翻,`scope_declarations` 恰恰就是为了允许合理的同名复用);扒宽扫描让无条件声明成立(否决:那是自带风险的另一个变更) | 5、9、附录 B、附录 C | +| 2026-09-17 | 为每个 `cross_runtime` 值写明产生它的条件,把逐值覆盖率从 68/149 提到 149/149,并用一个独立测试文件加以棘轮化 | 实现,Refs [#4447](https://github.com/huangruiteng/loopx/issues/4447) Track A;已落地于 [#4662](https://github.com/huangruiteng/loopx/pull/4662),合并 `11b857dec`,@huangruiteng 已批准 | 追加到 `test_semantic_vocabulary_drift.py` 末尾的 kernel 棘轮(否决:已有三个未合 PR 在该处冲突,而同一 diff 内的规则正是合并时最容易丢失的东西);为两个没有生产者的值推断含义(按证据规则否决:一旦写进表里,臆测的备注与经核实的备注无法区分);只记录由分支选择的值(否决:那会让由作者声明的值看起来像是没写,而“由作者声明”本身才是更有用的事实);用字符下限加非停用词计数来强制“不得只是复述”这条标准,并把未解析数量上限钉在 2(评审中双双否决:词数无法说明一条备注写出了产生条件,只会奖励灌水;而给诚实设预算会逼迫下一位作者编造条件,而不是如实记下证据缺失) | 附录 A、附录 B | ## 附录 C:证据登记