"""契约套件的装配点。 这套测试**不针对任何具体实现**。它写的是「不管你怎么实现,都必须满足这些行为」,所以它 自己不造实现,只声明「你得提供什么」。任何一个下游写完自己的存储或适配器,把它接到这里 的 fixture 上跑一遍,全绿就算合格——这是 `CLAUDE.md` §0 说的「任何新适配器的准入标准」。 **接法**:下游在自己的 `conftest.py` 里覆盖同名 fixture,返回自己的实现。 **现在这套测试全部跳过**,因为 `polyloop` 下还没有任何公共类型与 Protocol (`research-wiki/design/0006-public-names-and-signatures.md` 还没过人类门)。跳过的理由写在 每个 fixture 里,读跳过原因就能知道缺的是哪一块。 **为什么在实现之前就写它**:写一条契约测试要求把每一次调用逐字写出来——方法叫什么、参数 填什么、返回值怎么取。散文里读着通顺的地方,落到这一步就会露出来。前三轮文档评审抓不到的 洞,几乎全是这么冒出来的。 """ import pytest #: 公共类型与 Protocol 落地之前,套件里的每一条都缺同一样东西。 _NOT_YET = ( "polyloop 的公共类型与 Protocol 还没落地(design/0006 待确认)。" "这条契约要断言的行为已经写在测试的 docstring 里,落地之后去掉这个跳过即可。" ) @pytest.fixture def store(): """被测的存储接缝实现。 下游覆盖这个 fixture,返回自己的实例。每次调用应当返回一个**空的**存储——套件里的每条 测试都假设自己面对一份干净的日志,共用状态会让测试之间的顺序变成隐式依赖。 """ pytest.skip(_NOT_YET) @pytest.fixture def records(): """构造各类记录的辅助工厂。 它不是被测对象,是让测试正文读得懂的一层薄封装:`records.model_call_intent(...)` 比 直接写一长串构造参数更能看出这条测试在断言什么。工厂本身由库提供,因为记录类的字段 是库的公共承诺,下游不该为了跑契约测试去手写构造。 """ pytest.skip(_NOT_YET) @pytest.fixture def samples(): """被测解释器认得的几段模型输出,由实现方提供。 **套件不许自己写死输入。** 库不带默认解释器实现,也就不认识任何一家的动作语言:拿 dissect 的 Python 代码围栏去喂 GovDoc 的 JSON 解析器,后者正确地返回「无效决策」, 而写死输入的套件会把这个正确行为判成失败。 实现方要提供两段:`yields_an_action`(一段能被解释成动作的模型回复)与 `yields_invalid` (一段解释不出动作的)。这不是给套件开后门——「我这套语言里什么算合法动作」本来就只有 实现方答得出,套件断言的是**拿到之后的形状**,不是输入长什么样。 """ pytest.skip(_NOT_YET) @pytest.fixture def action_executor(): """被测的动作执行接缝实现。""" pytest.skip(_NOT_YET) @pytest.fixture def decision_parser(): """被测的决策解释接缝实现。""" pytest.skip(_NOT_YET) @pytest.fixture def model_client(): """被测的模型调用接缝实现。 注意这一层的契约测试**不打真实网关**——那是 e2e 的事。这里断言的是返回结构体的形状 与失败时的表达方式,用一个受控的替身就能验。 """ pytest.skip(_NOT_YET) @pytest.fixture def event_sink(): """被测的事件出口实现。""" pytest.skip(_NOT_YET)