feat(semantics): generate shared Turn contracts and controller rules (M2) - #4499
huangruiteng merged 13 commits into
Conversation
Signed-off-by: song <liusongstep@gmail.com>
Signed-off-by: song <liusongstep@gmail.com>
Signed-off-by: song <liusongstep@gmail.com>
Signed-off-by: song <liusongstep@gmail.com>
Signed-off-by: song <liusongstep@gmail.com>
Signed-off-by: song <liusongstep@gmail.com>
Signed-off-by: song <liusongstep@gmail.com>
…ptions Signed-off-by: song <liusongstep@gmail.com>
Signed-off-by: song <liusongstep@gmail.com>
Signed-off-by: song <liusongstep@gmail.com>
…bulary-m2-final Signed-off-by: song <liusongstep@gmail.com> # Conflicts: # docs/architecture/rfcs/semantic-vocabulary-convergence-v0.md # docs/architecture/rfcs/semantic-vocabulary-convergence-v0.zh-CN.md # docs/reference/glossary.md # examples/semantic-vocabulary-drift-smoke.py # loopx/control_plane/turn_driver/driver.py # loopx/control_plane/turn_driver/loop_controller.py # loopx/semantics/inventory_v0.json # loopx/semantics/production.py # loopx/semantics/vocabulary_v0.json # tests/architecture/test_semantic_production.py # tests/architecture/test_semantic_vocabulary_drift.py
302a10b to
55d185c
Compare
|
Rebased/merged English — three resolutions needed judgment, not marker deletion:
One test assertion was updated with the Q9 change: 中文 — 三处需要判断而非删标记: (1) Evidence at |
…owup Signed-off-by: song <22676124+songoow@users.noreply.github.com>
Replaces the unsigned web-UI merge ea69b88 with a byte-identical tree so the DCO gate passes. Brings in the two baseline fixes this PR's checks were failing on: loopx-project#4565 (refresh-state test patch targets) and loopx-project#4567 (chat_runtime reviewed module ceiling). The M2 Turn contract, its 29 ordered controller rules, and the generated Python/TypeScript bindings are unchanged. Signed-off-by: song <22676124+songoow@users.noreply.github.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ea69b88 to
445d871
Compare
huangruiteng
left a comment
There was a problem hiding this comment.
动机
评审 head:ea69b88ae4904868b2753e1dd6a82b78e9d57e42;base:main(merge-base = 43d3625323,因此本 PR 的可见 diff 已经只含 M2,M1 前置 #4494 已合入)。
在 base 上,"一个 Turn 的结论"这套词表有四处独立维护:LoopXTurnResultKind 在 transaction.py、LoopXTurnRoute 在 driver.py、LoopDisposition 在 loop_controller.py,TypeScript 的 settlement.ts 还自己写了一份 TURN_RESULT_KINDS;同时 controller 的 29 条决策优先级是 loop_controller.py 里的手写 if/elif 链,而 M1 的语义清单又是靠扫描这段源码里的字面量(LoopXTurnRoute.X: LoopDisposition.Y)来证明"这些值在生产中确实会被产出"。
代价是双向的:既有一份词表被四处维护的漂移风险,也有"规则一旦变成数据,原有静态证据就失效"的证据风险。本 PR 要做的正是把这两件事一起收口:词表、投影与规则表共用一个 JSON 源,生成 Python/TypeScript 绑定,同时给规则表换一套能站得住的产出证据。
改动思路
权威输入是新契约 loopx/control_plane/turn_loop_controller_contract_v0.json(schema loop_turn_controller_decision_contract_v0,其 SHA256 被写进两个生成件头部);scripts/generate_turn_contract.py 从它派生 turn_contract_generated.py/.ts;决策所有权仍在既有的 Python decide_loop_disposition,admission 仍由 ValidatedTurnReceipt、BoundedTurnBudget 和信封签名校验把守。
三个取舍我认为都站得住:
- 没有第二个决策引擎。
settlement.ts只是把本地数组换成import { TURN_RESULT_KINDS } from "./turn_contract_generated.ts"并原样 re-export,规则求值仍然只发生在 Python。 - 没有 unary 的"result → route"映射。作者明确说明同一个
validated_progress在不同 quota/前驱/预算下可以落到 run_now / replan / user gate / capability,因此生成的是有序规则表(10 条 admission check + 19 条 return,首个匹配胜出),而不是字符串查表。这一点是这份设计里最容易被做错、也是做得对的地方。 - 证据换引擎:规则变成数据后,静态字面量扫描不再成立,于是新增
loopx/semantics/turn_contract_witness.py,用固定合格输入真的调用 controller 与 projection,期望值在 witness 里独立声明。文档也把边界写清楚了——证明的是有限输入的 liveness,不是"任意伪造对象安全"。
具体改动
20 个文件、+3352/-398。结构上:生成件 1285 行(283 py + 524 ts,含枚举与规则表副本)、契约 JSON 478 行、生成器 302 行、witness 295 行、loop_controller.py 的求值改写(-398/+562)、测试约 800 行、文档 123 行,以及三处很窄的兼容改动。
关键代码讲解
loop_controller.py:639 decide_loop_disposition:新的求值器遍历 _LOOP_CONTROLLER_CONTRACT["rules"],inputs.fact(name) in values 做有限分区精确匹配,命中 check 行时必须先通过(否则 raise)才继续,命中 return 行即渲染结论;全部走完仍无匹配则 raise "controller contract has no disposition for the qualified inputs"。也就是说"没有默认分支"是刻意的失败闭合,而不是遗漏。
turn_contract_generated.py:283 project_turn_route:公开的 total 投影,contract_error 从原来的 KeyError 变成带信封语义的 ValueError;wait 与 blocked 都投影为 wait。driver.LoopXTurnRoute / transaction.LoopXTurnResultKind / loop_controller.LoopDisposition 都保留为兼容 re-export,所以外部 import 路径没变。
scripts/generate_turn_contract.py:266 verified_generated_paths:孪生计数豁免的唯一入口——它先 build_artifacts() 再逐字节比对,任何过期/缺失都 raise(stale generated Turn contract: <path>),不会因为文件名就放行。smoke 里对孪生预算的判定仍然打印 twins_raw、generated_verified、independently_maintained 三个数(实测 44 / 1 / 43,预算 43)。
loopx/semantics/turn_contract_witness.py:20:28 个 controller 用例 + 8 个投影用例,期望 disposition 与 reason 片段独立写在文件顶部,不来自生成表;collect_production 只在词表声明了 decide_loop_disposition 时才追加这些行。
对主干的风险
最强回归场景是"契约 JSON 改了但生成件没重生成"或"有人直接手改生成件",导致调用方读到一套词表、controller 执行另一套。防线是 --check 与 smoke 的 verified_generated_paths(),两者都是 raise 而不是修复或豁免——我做了变异验证:分别往两个生成件尾部追加一个字节后,--check 都报 stale Turn artifacts: <path> 且 exit=1,恢复后重新干净。
第二个风险是"新证据比旧证据弱"。这个我认可作者的处理:静态扫描在规则表变成数据后确实不可用,但 witness 执行的是真函数、期望值独立声明,并且明确只声称有限 liveness;我把 base 上未修改的 tests/test_loop_turn_loop_controller.py(70 例)、tests/test_turn_loop_disposition.py + tests/capabilities/test_periodic_report_pending_intent.py(61 例)以及 tests/control_plane_ts/turn_settlement.test.ts(19 例)原样拷到 head 上运行,全部通过——这比"作者自己新写的测试通过"更能说明行为保持。
P2(非阻塞):PR 描述仍写着"Do not merge this PR before M1"并引用旧的 M2-only 区间 2c7031721..e7231c534,还写着远端 CI pending。这些文字写于 rebase 之前:当前 head 的 merge-base 就是 43d3625323,前置已合入,可见 diff 已经只含 M2。建议在合并前把依赖与验证段落更新到当前 base/head,否则后来者会去追一个已经不存在的依赖。
P3(非阻塞):规则覆盖现在依赖 witness 模块;如果以后有人裁掉 witness 用例而不同步规则,覆盖可能仍绿。当前 tests/test_loop_turn_controller_contract.py 已把"每条规则都被 witness"钉住,保持这条断言即可。
语义与 CI 对齐
受影响契约:Turn 词表与投影、loop-controller 决策契约,以及语义清单的 production / 孪生 / 预算三项规则。结论是 reuse_existing:本批次沿用了 M1 已有的生成管线与 evaluate_maintainability_findings(reviewed_exceptions=...) 例外生命周期,没有新造豁免机制;REVIEWED_SEMANTIC_INVENTORY_EXCEPTIONS 为空,预算锚点未上调,BUDGET_ANCHOR 与注册表必须同 diff 移动(smoke 里 require 强制)。我另外做了一次反向验证:给它塞一条 ceiling 高于锚点的例外,smoke 立刻 FAIL stale inventory exception: semantic_inventory_budget:multi_value_twins(exit=1),说明这不是装饰性开关。
实测结果:generate_turn_contract.py --check → up to date(exit 0);semantic-vocabulary-drift-smoke.py --report → ok(coverage 26/26、owner_symbols 51/51、twins_raw=44 / generated_verified=1 / independently_maintained=43/43);pytest 聚焦 234 例通过;base 原版测试 70+61 例、TS 19 例通过。需要说明两点环境事实:TS 解析相关用例在没有 node_modules 时会失败,运行 npm ci --ignore-scripts 后 tests/architecture/test_semantic_production.py 30 例全过(该现象在 base 上同样存在,与本 PR 无关);本机 Node/SQLite 低于仓库声明下限,[*‑sqlite] 臂未运行。
我的整体评价
baseline(43d3625323)与 head(ea69b88ae)的对比:词表 owner 从"三处声明 + TS 一份数组"变为"一个契约 + 字节校验的派生对",controller 从手写优先级链变为同一优先级的生成表,投影值、拼法与 import 路径不变,inventory 预算结论在空例外表下与改前一致。没有可观测的决策漂移。
体量与收益匹配(change_proportionality: proportionate):真正产生漂移风险的四个 owner 被收成一个,且新机制全部落在既有 owner 内(turn_driver 与 semantics),没有新增 CLI/状态/权限面。repository_reuse: reused、typed_state_rule 用有限分区 + 失败闭合、authority_semantics: aligned(文档明确 controller 输出不授予 host launch / quota spend / 状态写权限)。
结论 APPROVE,两条建议均为非阻塞:先把 PR 描述的依赖段落对齐到当前 head(P2),以及保持"每条规则都被 witness"的断言(P3)。复评只需在 head 变化时重跑 --check、drift smoke 与上述三组测试。
English verdict: APPROVE - exact head ea69b88; the M2 batch collapses four owners of the Turn vocabulary into one contract with byte-verified generated bindings, keeps the Python controller as the only evaluator, and preserves observable behavior. Validated independently: generator --check clean plus mutation probes rejecting stale bindings, drift smoke ok (owner_symbols 51/51, twins_raw=44 generated_verified=1 independently_maintained=43/43), a stale reviewed exception refused, 234 focused Python tests, main's unmodified 70+61 Python and 19 TypeScript characterization cases passing at the head, and the real TS settlement suite green after npm ci. Two non-blocking notes: the PR body still declares the merged M1 prerequisite and a superseded M2-only range, and rule coverage now rests on the witness module staying pinned by tests.
huangruiteng
left a comment
There was a problem hiding this comment.
评审 head 变更说明
我先按 ea69b88ae4904868b2753e1dd6a82b78e9d57e42 完成了完整取证并发布过一条 review;发布期间该 PR 的 head 前进到 445d871d3268dba5ca59326130e133d1b5096834(一条 "Merge main into codex/semantic-vocabulary-m2-final" 的合并提交)。两条提交的目录树完全相同(ea69b88ae^{tree} == 445d871d3^{tree} == 29561ecd25df712e84c870242f2d96ec3501c7dd,git diff 为空),merge-base 仍为 43d3625323,因此前面的取证结论对当前 head 逐字适用;我仍在 445d871d3 上重跑了全部命令(见下文实测结果)。本条 review 取代上一条同内容 head 的 结论,请只以当前 head 为准。
动机
评审 head:445d871d3268dba5ca59326130e133d1b5096834;base:main(merge-base = 43d3625323,因此本 PR 的可见 diff 已经只含 M2,M1 前置 #4494 已合入)。
在 base 上,"一个 Turn 的结论"这套词表有四处独立维护:LoopXTurnResultKind 在 transaction.py、LoopXTurnRoute 在 driver.py、LoopDisposition 在 loop_controller.py,TypeScript 的 settlement.ts 还自己写了一份 TURN_RESULT_KINDS;同时 controller 的 29 条决策优先级是 loop_controller.py 里的手写 if/elif 链,而 M1 的语义清单又是靠扫描这段源码里的字面量(LoopXTurnRoute.X: LoopDisposition.Y)来证明"这些值在生产中确实会被产出"。
代价是双向的:既有一份词表被四处维护的漂移风险,也有"规则一旦变成数据,原有静态证据就失效"的证据风险。本 PR 要做的正是把这两件事一起收口:词表、投影与规则表共用一个 JSON 源,生成 Python/TypeScript 绑定,同时给规则表换一套能站得住的产出证据。
改动思路
权威输入是新契约 loopx/control_plane/turn_loop_controller_contract_v0.json(schema loop_turn_controller_decision_contract_v0,其 SHA256 被写进两个生成件头部);scripts/generate_turn_contract.py 从它派生 turn_contract_generated.py/.ts;决策所有权仍在既有的 Python decide_loop_disposition,admission 仍由 ValidatedTurnReceipt、BoundedTurnBudget 和信封签名校验把守。
三个取舍我认为都站得住:
- 没有第二个决策引擎。
settlement.ts只是把本地数组换成import { TURN_RESULT_KINDS } from "./turn_contract_generated.ts"并原样 re-export,规则求值仍然只发生在 Python。 - 没有 unary 的"result → route"映射。作者明确说明同一个
validated_progress在不同 quota/前驱/预算下可以落到 run_now / replan / user gate / capability,因此生成的是有序规则表(10 条 admission check + 19 条 return,首个匹配胜出),而不是字符串查表。这一点是这份设计里最容易被做错、也是做得对的地方。 - 证据换引擎:规则变成数据后,静态字面量扫描不再成立,于是新增
loopx/semantics/turn_contract_witness.py,用固定合格输入真的调用 controller 与 projection,期望值在 witness 里独立声明。文档也把边界写清楚了——证明的是有限输入的 liveness,不是"任意伪造对象安全"。
具体改动
20 个文件、+3352/-398。结构上:生成件 1285 行(283 py + 524 ts,含枚举与规则表副本)、契约 JSON 478 行、生成器 302 行、witness 295 行、loop_controller.py 的求值改写(-398/+562)、测试约 800 行、文档 123 行,以及三处很窄的兼容改动。
关键代码讲解
loop_controller.py:639 decide_loop_disposition:新的求值器遍历 _LOOP_CONTROLLER_CONTRACT["rules"],inputs.fact(name) in values 做有限分区精确匹配,命中 check 行时必须先通过(否则 raise)才继续,命中 return 行即渲染结论;全部走完仍无匹配则 raise "controller contract has no disposition for the qualified inputs"。也就是说"没有默认分支"是刻意的失败闭合,而不是遗漏。
turn_contract_generated.py:283 project_turn_route:公开的 total 投影,contract_error 从原来的 KeyError 变成带信封语义的 ValueError;wait 与 blocked 都投影为 wait。driver.LoopXTurnRoute / transaction.LoopXTurnResultKind / loop_controller.LoopDisposition 都保留为兼容 re-export,所以外部 import 路径没变。
scripts/generate_turn_contract.py:266 verified_generated_paths:孪生计数豁免的唯一入口——它先 build_artifacts() 再逐字节比对,任何过期/缺失都 raise(stale generated Turn contract: <path>),不会因为文件名就放行。smoke 里对孪生预算的判定仍然打印 twins_raw、generated_verified、independently_maintained 三个数(实测 44 / 1 / 43,预算 43)。
loopx/semantics/turn_contract_witness.py:20:28 个 controller 用例 + 8 个投影用例,期望 disposition 与 reason 片段独立写在文件顶部,不来自生成表;collect_production 只在词表声明了 decide_loop_disposition 时才追加这些行。
对主干的风险
最强回归场景是"契约 JSON 改了但生成件没重生成"或"有人直接手改生成件",导致调用方读到一套词表、controller 执行另一套。防线是 --check 与 smoke 的 verified_generated_paths(),两者都是 raise 而不是修复或豁免——我做了变异验证:分别往两个生成件尾部追加一个字节后,--check 都报 stale Turn artifacts: <path> 且 exit=1,恢复后重新干净。
第二个风险是"新证据比旧证据弱"。这个我认可作者的处理:静态扫描在规则表变成数据后确实不可用,但 witness 执行的是真函数、期望值独立声明,并且明确只声称有限 liveness;我把 base 上未修改的 tests/test_loop_turn_loop_controller.py(70 例)、tests/test_turn_loop_disposition.py + tests/capabilities/test_periodic_report_pending_intent.py(61 例)以及 tests/control_plane_ts/turn_settlement.test.ts(19 例)原样拷到 head 上运行,全部通过——这比"作者自己新写的测试通过"更能说明行为保持。
P2(非阻塞):PR 描述仍写着"Do not merge this PR before M1"并引用旧的 M2-only 区间 2c7031721..e7231c534,还写着远端 CI pending。这些文字写于 rebase 之前:当前 head 的 merge-base 就是 43d3625323,前置已合入,可见 diff 已经只含 M2。建议在合并前把依赖与验证段落更新到当前 base/head,否则后来者会去追一个已经不存在的依赖。
P3(非阻塞):规则覆盖现在依赖 witness 模块;如果以后有人裁掉 witness 用例而不同步规则,覆盖可能仍绿。当前 tests/test_loop_turn_controller_contract.py 已把"每条规则都被 witness"钉住,保持这条断言即可。
语义与 CI 对齐
受影响契约:Turn 词表与投影、loop-controller 决策契约,以及语义清单的 production / 孪生 / 预算三项规则。结论是 reuse_existing:本批次沿用了 M1 已有的生成管线与 evaluate_maintainability_findings(reviewed_exceptions=...) 例外生命周期,没有新造豁免机制;REVIEWED_SEMANTIC_INVENTORY_EXCEPTIONS 为空,预算锚点未上调,BUDGET_ANCHOR 与注册表必须同 diff 移动(smoke 里 require 强制)。我另外做了一次反向验证:给它塞一条 ceiling 高于锚点的例外,smoke 立刻 FAIL stale inventory exception: semantic_inventory_budget:multi_value_twins(exit=1),说明这不是装饰性开关。
实测结果:generate_turn_contract.py --check → up to date(exit 0);semantic-vocabulary-drift-smoke.py --report → ok(coverage 26/26、owner_symbols 51/51、twins_raw=44 / generated_verified=1 / independently_maintained=43/43);pytest 聚焦 234 例通过;base 原版测试 70+61 例、TS 19 例通过。需要说明两点环境事实:TS 解析相关用例在没有 node_modules 时会失败,运行 npm ci --ignore-scripts 后 tests/architecture/test_semantic_production.py 30 例全过(该现象在 base 上同样存在,与本 PR 无关);本机 Node/SQLite 低于仓库声明下限,[*‑sqlite] 臂未运行。
我的整体评价
baseline(43d3625323)与 head(ea69b88ae)的对比:词表 owner 从"三处声明 + TS 一份数组"变为"一个契约 + 字节校验的派生对",controller 从手写优先级链变为同一优先级的生成表,投影值、拼法与 import 路径不变,inventory 预算结论在空例外表下与改前一致。没有可观测的决策漂移。
体量与收益匹配(change_proportionality: proportionate):真正产生漂移风险的四个 owner 被收成一个,且新机制全部落在既有 owner 内(turn_driver 与 semantics),没有新增 CLI/状态/权限面。repository_reuse: reused、typed_state_rule 用有限分区 + 失败闭合、authority_semantics: aligned(文档明确 controller 输出不授予 host launch / quota spend / 状态写权限)。
结论 APPROVE,两条建议均为非阻塞:先把 PR 描述的依赖段落对齐到当前 head(P2),以及保持"每条规则都被 witness"的断言(P3)。复评只需在 head 变化时重跑 --check、drift smoke 与上述三组测试。
English verdict: APPROVE - exact head 445d871; the M2 batch collapses four owners of the Turn vocabulary into one contract with byte-verified generated bindings, keeps the Python controller as the only evaluator, and preserves observable behavior. Validated independently: generator --check clean plus mutation probes rejecting stale bindings, drift smoke ok (owner_symbols 51/51, twins_raw=44 generated_verified=1 independently_maintained=43/43), a stale reviewed exception refused, 234 focused Python tests, main's unmodified 70+61 Python and 19 TypeScript characterization cases passing at the head, and the real TS settlement suite green after npm ci. Two non-blocking notes: the PR body still declares the merged M1 prerequisite and a superseded M2-only range, and rule coverage now rests on the witness module staying pinned by tests.
…ule (#4571) M2 (#4499) moved `LoopXTurnResultKind` to `turn_contract_generated.py` and kept `transaction.py` as a compatibility re-export. The Python producer scanner attributes a site only when the owner class is imported from the owner's own module (`python_production._qualified_bindings`), so every `LoopXTurnResultKind.*` production site in `executor.py` became "not proven safe" although executor.py did not change in M2. Import the enum from the generated owner. It is the same enum object through both paths, so behavior is unchanged; the compatibility export stays for external readers. `examples/semantic-vocabulary-drift-smoke.py --report` on main vs this change: unresolved_producer_sites 52 -> 42 (distinct sites 34 -> 32), resolving `_host_result_stage` (2), `_run_task_validator` (7) and `_task_validation_stage` (1); no site becomes newly unresolved; smoke still reports ok. Refs #4447 (Track B, B2 producer pilot for the Turn kernel vocabularies). Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
conflicting_values 18->16 and conflicting_definitions 59->55 pin the measured values #4499 earned; both sides of each budget move in this one diff so the equality anchor holds. multi_value_twins stays 19 here: the multi-value single-source batch (#4617) owns that counter and locks it to 13. The inventory report line now discloses unlocked headroom (slack=key=N). The guard only fails on overflow, so a merge that reverts a tightened budget (registry and anchor move back together, the merge-trap shape) used to pass silently; now the reopened headroom is visible in the smoke output and in the PR diff of any run after it. Signed-off-by: song <liusongstep@gmail.com>
Dependency and review scope
Draft continuation of #4494 (M1), related to #4447. Base is
main; until #4494 lands, GitHub's full diff includes that prerequisite. Do not merge this PR before M1. The M2-only range is2c703172174f3e43bcb0f4d371d4a9b83f86ae00..e7231c534. Rebase onto main and rerun exact-head qualification after the prerequisite merges.Problem and resulting behavior
Turn result, route and loop-disposition sets were maintained separately, while a projection dictionary only described a slice of the actual controller decision. This change gives the three vocabularies, route projection and 29 ordered controller rules a shared JSON source. The existing Python controller consumes the generated rules; TypeScript settlement consumes the generated result set. Previous imports and every value spelling remain compatible. There is no new TypeScript decision engine or persisted-state migration.
The same result can lead to different dispositions depending on quota, predecessor and budget. The contract therefore preserves the full function and its rejection/precedence rules rather than inventing a result-string-to-route mapping. Independent qualified cases exercise actual outputs and refusals; an enum declaration or a value appearing in JSON is not production evidence.
The generated Python/TypeScript pair is excluded from independently maintained twin counts only after byte-for-byte regeneration checks. Raw physical counts remain visible. Owner coverage increases from 49 to 51 and has deletion-negative tests. Q7 reuses the existing maintainability evaluator for inventory-budget exceptions only: the reviewed map is empty, targets stay unchanged, and invalid/stale/growing exceptions fail. Other semantic gates cannot be waived through it.
Contributor workflow and costs
Edit
loopx/control_plane/turn_loop_controller_contract_v0.json, then run:Only regenerate artifacts affected by the source change; inspect the resulting diff. A behavior change also needs independently specified input/output and rejection cases. Do not modify generated files directly or treat generation as a proof of arbitrary data-flow safety. Checks reuse existing jobs. The public contract and bilingual RFC explain ownership, compatibility, provenance and exception repair.
Validation and independent review
loopx canary premerge: 11/11 selected checks plus four direct checks; no failures, warnings or manual holds.Touched entrypoints are the existing managed-step/Turn controller and settlement paths. Their output contract and settings are preserved, so no frontend configuration/editor change is required. Characterization, real CLI and installed-package checks cover adoption. The future-facing refactor removes independent owners and keeps one evaluator; no unused framework was added.
Remote CI for this head is pending. This PR is not merge authorization and does not complete the RFC: semantic debt consolidation, legacy-field retirement and migration-linked twin reduction remain.
中文说明
M2 把三套 Turn 词表、投影与完整决策表收敛到共享契约,保留已有拼法、导入与持久化兼容性,由原 Python 控制器继续执行。生产证据来自实际控制器案例;生成文件不能自行证明生产者存活。
生成孪生必须通过逐字节来源验证,才能从独立维护计数中排除。Q7 复用已有例外生命周期,当前例外表为空,没有提高预算或放宽其他守卫。上述本地验证和独立 review 已通过;远端检查与 M1 依赖尚待完成,暂保持草稿。M3/M4 和其余债务不在本阶段完成声明内。