feat(tools): 落成 executor() 与派生分发器;公共数据类一律只收关键字参数

两个自行调研后决定的问题,各自的证据写进了 design doc:

一、handler 返回 str 而不是带截断计数的小结构(0008 决策三,文末新增一节)。三条实据:
两个真实消费者的执行函数今天就返回纯字符串(GovDoc 的 handler 是 Coroutine[..., str],
dissect 的环境 execute 是 -> str);dissect 的 observation_truncated_chars 唯一的生产写入点
硬编码 0 且全仓零读取点,存在的是名字不是需求;reference/pi 是唯一把截断做完整的,它记的是
totalBytes/outputBytes/maxBytes 这组绝对量而不是一个差值——现在补 truncated_chars 补的
大概率是错形状,正是 scope.md 说的「猜出来的接缝比没有接缝更难拆」。

二、新增 design 0009:src/polyloop/ 下每个数据类都加 kw_only=True,另加一条扫描测试守它。
实验室七个仓库 223 个 dataclass 里 kw_only 出现零次,但那是默认行为不是选择。真正的证据是
PolyGateway:它的 LLMResponse 前 11 个字段顺序被三个下游的测试替身按位置构造锁死,模块
docstring 写着「字段顺序即公共承诺」,还得专门写一条 test_eleven_legacy_fields_positional
守着,从此再也插不进字段。那个约束不是它选的是它继承的,而本库还没有下游装上。
扫描测试查的是构造签名不是那个装饰器参数——要守的承诺是「按位置构造不了」。

executor() 在派生那一刻全查一遍实现,缺一个就报错,不拖到分发时才炸。RegistryExecutor 是
具体类而不是闭包,因为 RunRequest 要用 isinstance 认它。CancelledError 不被那个
except Exception 接住(它继承 BaseException),有测试守着。
This commit is contained in:
2026-08-10 01:19:15 -04:00
parent fa09e873c5
commit 6fafd95d6c
10 changed files with 1082 additions and 49 deletions
+79
View File
@@ -0,0 +1,79 @@
"""`research-wiki/design/0009-keyword-only-public-types.md`:数据类一律只收关键字参数。
守的是一条对下游的承诺——**字段顺序不受任何保护**,所以往中间插字段永远是安全的。没有这条
限制,只要有人能按位置构造,字段顺序就自动成了承诺的一部分,而插字段会静默改掉后面每一个
参数的含义:不报错,只是每个值都进错了字段。
实验室里已经有一个现成的教训:另一个库的一个 18 字段类型,前 11 个的顺序被三个下游的测试
替身按位置构造锁死,它从此再也不能往中间插东西,还得专门写一条测试守着。那个约束不是它选
的,是它继承的。本库还没有任何下游装上,所以它可以不长出来。
**这条测试断言的是承诺不是实现**(`CLAUDE.md` §1.8):构造方式是对外承诺过的东西。
"""
import dataclasses
import importlib
import inspect
import pkgutil
import pytest
import polyloop
pytestmark = pytest.mark.unit
def _all_dataclasses() -> list[type]:
"""遍历包内每一个模块,收集其中定义的数据类。
按 `__module__` 过滤,免得把一个模块 import 进来的别处的数据类重复算一遍——重复本身无害,
但它会让下面那条 fail-closed 守卫的计数虚高,于是守卫失去意义。
"""
found: dict[str, type] = {}
modules = [polyloop]
for info in pkgutil.walk_packages(polyloop.__path__, prefix="polyloop."):
modules.append(importlib.import_module(info.name))
for module in modules:
for name in dir(module):
candidate = getattr(module, name)
if not isinstance(candidate, type) or not dataclasses.is_dataclass(candidate):
continue
if candidate.__module__ != module.__name__:
continue
found[f"{candidate.__module__}.{candidate.__qualname__}"] = candidate
return list(found.values())
def test_the_scan_finds_something() -> None:
"""守卫自身的 fail-closed 检查。
没有这一条,包被改名或搬走之后下面那条断言会遍历一个空列表然后安静地绿,而绿的含义从
「全都合规」变成了「什么都没检查」,两者在输出上分不出来。
"""
found = _all_dataclasses()
assert len(found) >= 20, f"只扫到 {len(found)} 个数据类,扫描范围多半错了"
assert {cls.__module__ for cls in found} >= {"polyloop.types", "polyloop.ports"}
def test_every_dataclass_is_keyword_only() -> None:
"""包内每一个数据类都只收关键字参数,内部模块也一样。
统一规则不留判断余地,也才写得成这条检查。按类型大小分档的话,每加一个类型都要判一次,
而判错那次不会当场报错。
**查的是构造签名,不是那个装饰器参数。** 要守的承诺是「按位置构造不了」,而
`kw_only=True` 只是达成它的一种写法——逐字段标 `field(kw_only=True)` 是另一种,将来
Python 再多一种也一样。查签名的话这条检查不会因为写法换了就漏掉。
"""
positional: list[str] = []
for cls in _all_dataclasses():
offenders = [
name
for name, parameter in inspect.signature(cls).parameters.items()
if parameter.kind is not inspect.Parameter.KEYWORD_ONLY
]
if offenders:
positional.append(f"{cls.__module__}.{cls.__qualname__}: {offenders}")
assert positional == [], f"这些数据类还能按位置构造:{positional}"