Skip to content

fix(turn-driver): import the Turn result owner from its generated module - #4571

Merged
huangruiteng merged 1 commit into
loopx-project:mainfrom
songoow:codex/executor-import-generated-owner
Sep 16, 2026
Merged

huangruiteng merged 1 commit into
loopx-project:mainfrom
songoow:codex/executor-import-generated-owner

Conversation

@songoow

@songoow songoow commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • M2 (feat(semantics): generate shared Turn contracts and controller rules (M2) #4499) moved LoopXTurnResultKind into turn_contract_generated.py and left transaction.py as a compatibility re-export. The Python producer scanner (loopx/semantics/python_production.py, _qualified_bindings) attributes a production site only when the owner class is imported from the owner's own module, so all LoopXTurnResultKind.* production sites in executor.py became unknown_producer after M2 even though executor.py did not change.
  • Import the enum from the generated owner (one line). It is the same enum object through both paths; behavior is unchanged and the compatibility export stays for external readers.
  • Other transaction importers (command_validation.py, managed_step.py, settlement.py, __init__.py) only read or compare the enum and have no production sites, so they are left alone to keep the diff to the measured effect.

Issue Or Task

Validation

  • Tested revision: cda1133
  • Run state: finished
  • Input classes: public_fixture
Check kind Result Public-safe evidence / limitation
static passed python -m py_compile loopx/control_plane/turn_driver/executor.py; import identity asserted: transaction.LoopXTurnResultKind is turn_contract_generated.LoopXTurnResultKind is executor.LoopXTurnResultKind.
regression_parity passed python examples/semantic-vocabulary-drift-smoke.py --report on main vs head: unresolved_producer_sites 52 → 42, distinct sites 34 → 32. Resolved: executor.py::_host_result_stage (2), _run_task_validator (7), _task_validation_stage (1). No site becomes newly unresolved; every other summary line identical; smoke still ok.
integration passed loopx canary premerge --from-git-diff --git-diff-base origin/main (tier standard, run from the checkout): ok: true, 10 checks selected, 0 failures.
unit not_run pytest is not installable in the authoring sandbox; CI test-shard is the authoritative run. No test patches executor.LoopXTurnResultKind by path, and no anchor pins the unresolved-site count (PRODUCER_VOCABULARY_ANCHOR / RETURN_PRODUCER_ANCHOR cover registry coverage, not this metric).
  • Coverage and gaps: the change is an import-source swap for one symbol; the parity row measures the only intended effect (producer attribution) and shows no collateral change. The remaining 42 unresolved sites are pre-existing dynamic/parameter paths (e.g. project_turn_route, _ControllerInputs.render, driver.py::build_loopx_turn_plan) that need the B2 slot analysis, not an import fix.

Frontend / Visual Evidence

N/A — no user-visible change.

🤖 Generated with Claude Code

M2 (loopx-project#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 loopx-project#4447 (Track B, B2 producer pilot for the Turn kernel vocabularies).

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

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@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.

动机

评审 head:cda113317f1416df6b13420c1ce06b537c20649f;base:main(merge-base f1166e81e)。

这是一条被测量出来的回归修复:#4499(M2) 把 LoopXTurnResultKind 搬进生成模块 turn_contract_generated.py,并把 transaction.py 留成兼容 re-export;而 Python producer 扫描器(loopx/semantics/python_production.py::_qualified_bindings)只在"owner 类从 owner 自己的模块被导入"时才承认一个生产点。于是 executor.py 里所有 LoopXTurnResultKind.* 写入点在自己没改动的情况下变成了 unknown_producer。

代价是证据层面的:M2 的目的正是让"Turn 结果词表确实被生产"这件事可被证明,而这次 attribution 丢失恰好削弱的就是这个不变量;运行期行为没有任何变化。

更小的修法只有这一条:要么回退 M2(把三套词表 owner 重新拆开),要么让扫描器学会穿透 re-export 链(语义更大、且带来误归因风险),要么把每个 importer 都改一遍(未被测量的改动)。作者选了"只改有生产点的那个模块"的一行,并明确把其余 importer 留给 Track B B2 分析——这个取舍是对的。

改动思路

入口是 examples/semantic-vocabulary-drift-smoke.py --report,权威输入是注册表派生的 owner 表(loopx/semantics/vocabulary_v0.json)加上各模块真实的 import 语句,判定权在 scan_python_production/_qualified_bindings。

改动本身是一次 import 源切换:LoopXTurnResultKind 不再从 .transaction(兼容 re-export)取,而是从 .turn_contract_generated(owner)取。运行期绑定的是同一个类对象(我在进程内断言过 transaction.LoopXTurnResultKind is turn_contract_generated.LoopXTurnResultKind is executor.LoopXTurnResultKind),所以行为不变,变的只是扫描器能否把这个模块的生产点归到 owner 名下。

这个"import 源具有语义"的规则本身没有被本 PR 修改,也没有为了这一处去特化扫描器——这符合仓库"每个词表一个 owner、兼容层要有理由"的既有约定。

具体改动

1 个文件、+1/-1:loopx/control_plane/turn_driver/executor.py 的 import 块——从 .transaction 的导入组里删掉 LoopXTurnResultKind,改为单独一行 from .turn_contract_generated import LoopXTurnResultKind。executor 内 30 处枚举使用点全部保持不变。

关键代码讲解

executor.py:76 的这行 import 是唯一的语义位点:扫描器对 ImportFrom 的别名做 (_module(path), name) 查表,只有解析到 owner 模块才把 owner 类放进 bindings;解析不到就把它从 bindings 移除,后续该模块的写入点被报成 unknown_producer(而不是猜一个 owner)。

python_production.py:141 _qualified_bindings 是这条规则的实现者,本 PR 没有改它——这正是这条修复值得肯定的地方:作者没有为了让自己的模块通过而去放宽规则,而是把自己的模块挪回规则之内。我在同一个 worktree 里把 import 改回 .transaction 复现了基线(52),改回 owner 后回到 42,且 unknown 列表的差异恰好是 PR 声称的 10 条(_host_result_stage 2 条、_run_task_validator 7 条、_task_validation_stage 1 条),没有任何新增行。

对主干的风险

最强回归场景是"以后有人再次把 import 挪到 re-export 上",此时 attribution 会静默退化:unresolved_producer_sites 从 42 涨回 52,但 smoke 仍然 ok、215 条聚焦测试也照常通过——也就是说这条修复目前没有门禁保护。这是下面 P3 的依据,而不是反对本次合并的理由。

爆炸半径只在语义清单的证据层:没有运行期、状态、CLI 或权限面改动。我实测:head 上 smoke ok(unresolved_producer_sites=42);回退 import 后 ok(52);两边都没有新增 unresolved 行;pytest 聚焦 215 例通过(含 tests/test_loopx_turn_transaction.py、tests/test_loop_turn_loop_controller.py、tests/architecture/test_semantic_production.py、tests/architecture/test_turn_contract_generation.py)。测试里没有按路径 patch executor.LoopXTurnResultKind,所以这次 import 源变更不会打断任何 mock。

P3(非阻塞):把这条被修好的指标钉住。建议给 unresolved_producer_sites(或至少 executor 这几行)加一个锚点断言,让"再次回退 import"变成红灯,而不是只让一个数字变化。

P3(非阻塞):PR 描述里"The other transaction importers … have no production sites"这句偏强。loop_controller.py 仍有 7 处枚举成员使用,其 _ControllerInputs.render/_envelope_route 仍在 unresolved 列表里(属于本 PR 明确延后的动态/路由型路径)。范围判断没问题,但措辞会让下一位读者误以为那些模块已被归因;建议改成"这些模块没有新增可归因点,其既有动态路径保持 unresolved"。

我的整体评价

baseline(f1166e81e)与 head 的对比:unresolved_producer_sites 52→42,unknown 列表只有删除、无新增,枚举 identity 与运行期行为不变,smoke 两边都 ok。这是一条可复现、可回滚、作用范围精确的修复。

体量与影响匹配(change_proportionality: proportionate):一行修复一个被测量的证据回归,没有新增抽象、没有放宽扫描器规则、没有碰兼容导出。repository_reuse: reused(复用既有 owner 与兼容层),typed_state_rule 保持"未解析即 unknown"的失败方式,authority_semantics 不适用(无 actor/权限面)。

结论 APPROVE,两条 P3 均非阻塞。复评只需在 head 变化时重跑上述 smoke 两条状态对比与聚焦测试。

English verdict: APPROVE - exact head cda1133; the one-line import move restores producer attribution for executor.py without changing runtime behavior or the compatibility re-export. Reproduced both directions in one worktree: unresolved_producer_sites 52 -> 42, the unknown-list diff is exactly the ten claimed executor sites with no additions, enum identity holds through both paths, smoke ok and 215 focused tests pass. Two non-blocking P3s: the repaired metric is not pinned by any anchor (a revert stays green), and the PR body's claim that the other importers have no production sites is imprecise for loop_controller.py.

@huangruiteng
huangruiteng merged commit 023bcbc into loopx-project:main Sep 16, 2026
22 of 26 checks passed
songoow pushed a commit to songoow/loopx that referenced this pull request Sep 16, 2026
Rebaselines this branch on main after loopx-project#4571 merged the executor import fix,
so the producer-site measurement in the PR body is taken against the real
base rather than the pre-loopx-project#4571 tree.

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

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@songoow
songoow deleted the codex/executor-import-generated-owner branch September 28, 2026 03:00
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