Skip to content

fix(types): restore kernel contract checks for decision scope - #4257

Merged
huangruiteng merged 1 commit into
loopx-project:mainfrom
songoow:codex/mypy-decision-scope-casts
Sep 11, 2026
Merged

huangruiteng merged 1 commit into
loopx-project:mainfrom
songoow:codex/mypy-decision-scope-casts

Conversation

@songoow

@songoow songoow commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Summary

python -m mypy fails on current main with seven no-any-return errors, all in loopx/control_plane/todos/decision_scope.py. That step is required, and merge-gate rejects any incomplete qualification, so every open pull request currently inherits the failure.

The module's private _evaluate helper validates one typed effect-runtime result and returns Any, while every public wrapper already declares a concrete dict, bool, or list return type. Strict mode therefore rejects each call site.

Change

Annotate the seven call sites with cast(...) to the return type they already promise.

This is a type-level annotation only. No runtime branch, value, or call sequence changes, and _evaluate keeps its single validated boundary.

Why standalone

The identical fix is currently carried inside #4233, whose review is blocked on chat asset retention and generated-asset deletion. Extracting the two-line semantic change here unblocks the shared required check without depending on that PR. If #4233 merges first, this branch becomes a no-op and can be closed.

Validation

  • python -m mypy -> Success: no issues found in 22 source files
  • Focused tests -> 56 passed:
    • tests/control_plane/test_todo_decision_scope_consistency.py
    • tests/control_plane/test_todo_decision_scope_lifecycle.py
    • tests/control_plane/test_todo_decision_scope_cli_validation.py
    • tests/control_plane/test_user_gate_lane_progress.py
    • tests/control_plane/test_scoped_gate_successor_tool_behavior.py
  • python examples/control_plane/bounded-context-namespace-smoke.py -> ok

Risk

Minimal. cast performs no runtime conversion, so the executed behavior is byte-identical to main. The pre-existing I001 import-sort finding for this file is unchanged by this patch and is not part of the CI ruff rule selection.

`python -m mypy` fails on current main with seven `no-any-return` errors in
`loopx/control_plane/todos/decision_scope.py`. The module's private
`_evaluate` helper validates one typed effect-runtime result and returns
`Any`, but every public wrapper declares a concrete `dict`, `bool`, or list
return type, so strict mode rejects each call site.

Annotate the seven call sites with `cast(...)` to the return type they
already promise. This is a type-level annotation only: no runtime branch,
value, or call sequence changes, and `_evaluate` keeps its single validated
boundary.

The same fix is currently carried inside loopx-project#4233, which is blocked on chat
asset retention changes. Every pull request inherits this failure through
the required `Type-check kernel contracts` step and the `merge-gate`, so it
is extracted here as a standalone repair.

Validation:
- `python -m mypy` -> Success: no issues found in 22 source files
- `python -m pytest tests/control_plane/test_todo_decision_scope_consistency.py
  tests/control_plane/test_todo_decision_scope_lifecycle.py
  tests/control_plane/test_todo_decision_scope_cli_validation.py
  tests/control_plane/test_user_gate_lane_progress.py
  tests/control_plane/test_scoped_gate_successor_tool_behavior.py` -> 56 passed
- `python examples/control_plane/bounded-context-namespace-smoke.py` -> ok

Signed-off-by: song <liusongstep@gmail.com>
@huangruiteng
huangruiteng merged commit 5b163c3 into loopx-project:main Sep 11, 2026
19 checks passed
@songoow
songoow deleted the codex/mypy-decision-scope-casts branch September 16, 2026 05:54

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

审阅对象:PR #4257,exact head 78573ba0423c2a12cc13a99e1f8387cb6b69bec8,合并提交 78573ba,base main。本次审阅该 exact head 的完整增量(1 file, +14/-10),并核对了它声称修复的父侧失败。

动机

这个 PR 修的是"共享 main 上的必需检查坏掉,导致每个 PR 都被连带阻塞"。当时 python -m mypy 在 main 上失败,七处 no-any-return 全部位于 loopx/control_plane/todos/decision_scope.py;由于类型检查是必需步骤,merge-gate 不接受不完整的资格证明,于是任何开放中的 PR 都继承了这次失败,与它们自身改动无关。

这不是纯风格问题:该模块是 Todo decision scope 的 Python 编解码边界,私有的 _evaluate 返回 Any,而每个公开函数都声明了具体返回类型(dict[str, Any]、dict[str, Any] | None、bool、list[list[dict[str, Any] | None]])。声明与实现不一致意味着调用方拿不到类型保护,同时必需门禁无法通过。

改动思路

思路是"在真正的边界上说真话",而不是把检查静音:

  • 保留既有的 _evaluate(它仍然校验 todo_decision_scope_result_v0 信封,非法投影照旧抛 TypeError),只在七个公开函数的返回处用 typing.cast 标注该 operation 真实产生的类型。
  • cast 在运行时是恒等函数,因此调用顺序、分支、取值、错误文案、控制流都没有变化;改动纯粹是签名层面的。
  • 每个 cast 都与 TypeScript owner 的分支类型逐一对齐:consistency/relation/scope_relation/exact_relation -> JsonObject(或 null)、standing -> JsonObject | null、covers -> boolean、relations -> 嵌套 (JsonObject | null)[][]。这正是 evaluateDecisionScope 声明的联合类型 JsonObject | boolean | null | (JsonObject | null)[][] 的分支展开。

没有引入 Python 侧的第二套校验(那会与 TS owner 重复且增加热路径成本),没有用 # type: ignore 掩盖契约,也没有改动 wire schema。

具体改动

唯一文件 loopx/control_plane/todos/decision_scope.py(+14/-10):from typing import Any, cast;为 standing_decision_authority_for_agent、build_required_decision_scope_consistency、decision_scope_covers、decision_scope_gate_relation、exact_todo_gate_relation、todo_gate_relation、todo_gate_relations 七个函数增加与声明类型一致的 cast(...)(部分调用被折行,因此删除行数与新增行数接近)。

关键代码讲解:

  1. _evaluate(未改动):effect_runtime_result("todo.decision_scope.evaluate", ...) 之后校验信封 schema,返回 result["result"]。它仍是唯一的运行时守门人。
  2. decision_scope_covers:保留 if not gate or not required: return False 短路,再 cast(bool, _evaluate("covers", ...))——与 TS 的 boolean 分支一致。
  3. todo_gate_relations:保留空输入时 [[] for _ in gates] 的位置语义,再 cast(list[list[dict[str, Any] | None]], _evaluate("relations", ...))——与 TS 的嵌套数组分支一致。
  4. standing_decision_authority_for_agent / *_relation:None 的预检与 _facts 归一化顺序不变,只在返回处声明 dict[str, Any] | None。
  5. build_required_decision_scope_consistency:源归一化、注册 agent 排序与 authority 处理全部保持原样,仅返回类型被声明为 dict[str, Any]。

对主干的风险

纯类型标注改动,运行时行为等价:cast 不改值、不改分支、不改错误文案,因此不存在功能回归路径。唯一需要审视的是"cast 是否掩盖了不真实的类型"——本轮逐一比对了 TS 分支,七个 cast 都与 owner 的实际分支类型相符,没有夸大。

需要如实记录的残余风险:cast 不做运行时校验,因此如果未来某个已知 operation 的返回形状发生变化,mypy 不会再报错;承载该风险的是 TS owner 的联合类型与测试,以及"新增 operation 时必须同时扩展两侧"的约定。另一种理论风险是 covers 若返回真值对象,mypy 仍会通过——这正是 cast 的固有边界,也是我把"与 TS 联合类型比对"当作本次主要证据、而不只看注解的原因。

父侧证据(确认它修的就是真实失败):在本 PR 之前的 checks 作业日志中(#4251 合并头 fa57253 的 job 103344649803)可以直接看到 loopx/control_plane/todos/decision_scope.py:93: error: Returning Any from function declared to return "dict[str, Any] | None" [no-any-return],以及 104/196/200/204 行的同类错误——与本 PR 的定位一致。修复后该 head 的 23 项检查全绿(含 checks、kernel-static-checks、pytest、merge-gate)。

本地验证(该 exact head 的独立 worktree):python -m mypy -> Success: no issues found in 22 source files;pytest -q tests/control_plane/test_todo_decision_scope_consistency.py tests/control_plane/test_todo_decision_scope_lifecycle.py tests/control_plane/test_todo_decision_scope_cli_validation.py tests/control_plane/test_user_gate_lane_progress.py tests/control_plane/test_scoped_gate_successor_tool_behavior.py -> 56 passed in 66.85s。

我的整体评价

APPROVE。这是一个范围极小、目标明确的基础设施修复:它把"必需类型检查在 main 上失败、所有 PR 被连带阻塞"的状态恢复为绿色,做法是在既有编解码边界上如实声明返回类型,而不是用 type: ignore 掩盖或用 Python 侧重复校验替代 owner。七个 cast 与 TypeScript owner 的联合类型分支逐一对应,运行时行为完全等价,56 项聚焦测试与仓库级 mypy 都在该 head 通过,并且父侧日志明确记录了它消除的错误。

无阻断发现。建议性提示(非阻断):cast 不校验运行时形状,因此后续若新增 decision-scope operation,应同步扩展 TS 联合与这里的注解;如果将来希望在 Python 侧对形状做廉价断言,可以在不引入第二套规则的前提下对少数关键 operation 增加轻量检查。作为合并后审计记录,本结论只针对该 exact head,不授予任何合并、回滚或再次修改的权限。

English verdict: APPROVE - #4257 at exact head 78573ba (community contribution by songoow) restores the repository's required kernel type-check for loopx/control_plane/todos/decision_scope.py by declaring the seven public codec returns with typing.cast instead of returning Any, matching the TypeScript owner's per-operation union (consistency/relation/scope_relation/exact_relation -> object or null, standing -> object or null, covers -> boolean, relations -> nested array) while _evaluate's envelope validation, all guards, call order and error texts stay unchanged; cast is runtime-identity, so behavior is equivalent. Verified at the exact head: python -m mypy -> Success: no issues found in 22 source files, 56 focused decision-scope/lane tests pass, and hosted checks are 23/23 green including merge-gate; the parent-side checks log records exactly the no-any-return errors this change removes, which had been blocking every open pull request. No blocking finding; residual limit is the inherent cast blind spot (a future inner-shape drift is guarded by the TS owner's union and tests, not by mypy), and any new operation must extend both sides.

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