docs: 回写全仓库对契约套件的指向,以及 README、架构与 CHANGELOG
套件从 tests/contract/ 搬进 polyloop.testing 之后,全仓库 28 处引用要重新指过。修了 12 处, 其余在 design/(只增不改)与 scratch/(由人清理)里。 **CLAUDE.md 改了四处事实**:§0 权威表里行为契约的权威、§0 那句依赖规则的条数、§5 目录树与 模块数、§1.8 那句「谁断言公共 Protocol 的签名」。§1 的其余硬约束与 §2 的人类门一条没动。 **architecture.md**:分层图第 4 层加一格,装配层从三个变四个;代码地图加一行;第十节按代码 逐项重写——那笔「工具段渲染样式」的欠账**没有被数字对上盖掉**,加了 fingerprints 之后请求 的字段数恰好还是十一,而组成已经换过,所以那一节正面写着它仍然欠着;新增第十条依赖规则 (pytest 只在 testing 那个 extra 里,别处 import 它会让下游的生产环境一 import 本库就 ModuleNotFoundError),带静态与运行时两半;删掉「src/ 下一行代码都没有」那段过期状态说明; 决策索引补齐 0008 到 0016,其中四行原描述说的不是那份文档真正定的东西。 **migrations/dissect.md** 那笔「内存实现不存在」的欠账还掉了。 **压测那边**三条测试守的是一条已经撤销的公共契约,改名并写清它们现在守的是场景自己的选择。 AppWorld 那处刻意的偏离(不补三个反引号)留着不恢复——那条路径要模型输出被 stop 序列截断才 触发,而压测不配 stop 序列,恢复的收益不抵重跑一次压测的成本。但注释的理由改对了:它现在是 一笔有出处的欠账,不是一个决定。 CHANGELOG 攒在「未发布」段,版本号不提前写(§1.10)。
This commit is contained in:
@@ -9,6 +9,102 @@
|
||||
|
||||
## 未发布
|
||||
|
||||
**版本号在真的要发布的那一刻才定,这里不提前写。** 只 bump 版本号不叫发布(`CLAUDE.md`
|
||||
§1.10):PolyGateway 的 1.0.6 与 1.1.0 都完成了版本号 bump 与 CHANGELOG,却从未上传,registry
|
||||
长期停在 1.0.5,下游 `pip install` 拿不到任何修复且无人发现。提前把号写进这一段就是在重演
|
||||
那个形态——读到号的人会以为那一版已经在 registry 上,而它不在。
|
||||
|
||||
这一批改动回应的是下游项目在 PolyLoop 仓库上提的五个 issue。
|
||||
|
||||
### 契约套件随包发布
|
||||
|
||||
五个接缝的公共行为一致性用例从 `tests/contract/` 搬进 `polyloop.testing`,跟着 wheel 一起
|
||||
装到下游去。**`pip install polyloop` 之后 site-packages 里没有 `tests/`**,所以在此之前那套
|
||||
被称作「任何新适配器的准入标准」的用例,第一个下游根本拿不到。
|
||||
|
||||
**接法同时换了**:原来是在自己的 `conftest.py` 里覆盖同名 fixture,现在是继承契约基类。
|
||||
|
||||
```python
|
||||
from polyloop.testing import RunStoreContract
|
||||
|
||||
class TestMyPostgresStore(RunStoreContract):
|
||||
@pytest.fixture
|
||||
def store(self, pg_pool): return MyPostgresStore(pg_pool)
|
||||
```
|
||||
|
||||
换掉是因为覆盖同名 fixture 在装到 site-packages 之后无处落脚——pytest 的 `conftest.py` 只沿着
|
||||
被收集文件的目录链往上找,而套件所在的那条链在 site-packages 里,看不见下游仓库里的
|
||||
`conftest.py`。继承则不需要任何 `conftest.py` 魔法:子类定义在下游自己的测试文件里。顺带一个
|
||||
接缝可以接多个实现,各写一个子类。
|
||||
|
||||
跑它要装 `polyloop[testing]`(pytest 与 pytest-asyncio,版本只写下界,不和下游已经在用的
|
||||
pytest 打架)。async 用例的事件循环归下游管:把 `asyncio_mode` 设成 `"auto"`,或者自己给子类
|
||||
打标记。本包还声明了一个 pytest 插件入口,它唯一的作用是让 pytest 重写这些模块里的
|
||||
`assert`,失败时打印出等号两边的实际值。
|
||||
|
||||
**基类名、基类上的 fixture 名、每一条用例的方法名从此是公共承诺**,改名的代价和改公共类型的
|
||||
字段一样。
|
||||
|
||||
### 新增一个内存存储实现
|
||||
|
||||
`polyloop.stores.VolatileRunStore`:日志攒在进程内存里,进程一退就没了。它不提供的是**跨进程
|
||||
恢复**,不是「读不回来」——同一个进程里写进去的意图照样读得回来,它和逐行追加那个实现跑的是
|
||||
同一套存储契约。桶里存的是编码后的载荷、读的时候才解码,所以调用方后来改自己手里那个 dict
|
||||
改不到已经写下去的快照,读回来的日志被就地改动也污染不了存储本身——落盘那个实现每次都重新
|
||||
解析文件,天然如此,这个实现靠同一条路径对齐它。一条字段类型不对的记录在两个实现上的下场也
|
||||
一样:写得进去,读的时候抛同一个解码错误。
|
||||
|
||||
对下游的意义是测试和「我不要跨进程恢复」那一档不必再自己写一个存储:存储接缝是必填的,
|
||||
在此之前不想落盘的人只能自己造一个。关系数据库那种形态仍然由下游自己实现。
|
||||
|
||||
### `RunRequest` 新增 `fingerprints` 字段
|
||||
|
||||
`Mapping[str, str]`,默认空映射,**已有代码不受影响**。它记的是这次运行用的材料是哪一版——
|
||||
提示词模板的哈希、技能库的版本这类——全部键值以 `request.fingerprint.<name>` 进参数快照,
|
||||
不透传给模型调用。
|
||||
|
||||
它和 `model_binding` 的分界是「坐标还是配方版本」:绑定记这次运行属于哪一格(哪个账本、
|
||||
第几轮、哪道题),指纹记这次用的材料是哪一版。**一条指纹都没有时快照里一个键都不写**,
|
||||
所以今天已经在跑的配置算出来的快照逐字节不变。
|
||||
|
||||
同时补上一道校验:`fingerprints` 与 `model_binding` 的键值必须都是字符串,在构造请求时就拒绝
|
||||
非字符串;五个接缝 `parameters()` 上报的键值在聚合成快照时同样校验。在此之前这类值要等到续跑
|
||||
读日志的那一刻才炸——那时这次运行已经跑完、钱已经花了。
|
||||
|
||||
### ⚠ 参数快照里注入内容的键形状变了
|
||||
|
||||
**这是这一批里唯一一处会让已有行为变化的改动。** 注入的条目标识原来拍平成一个键
|
||||
`request.injected_entry_ids`,现在按通道分组,一个通道一个键
|
||||
`request.injected_entry_ids.<通道名>`。
|
||||
|
||||
**用旧版本跑了一半的运行,升级之后 `resume` 会抛 `ParameterDriftError`。** 失败是响亮的,不是
|
||||
静默的:错误信息把漂移的键逐个列出来,能看到旧的那个键消失、新的那几个键出现。处置有两条,
|
||||
和快照里任何一项变了时一样——那次运行重新开始,或者接受它跑不完。跑完了的运行不受影响,
|
||||
参数快照只在续跑时被比对。
|
||||
|
||||
改形状是因为拍平之后「声明了这个通道但一条都没选中」和「压根没有这个通道」得出同一个结果,
|
||||
而有下游要比较的正是这两种情形。分组之后前者是一个值为空串的键,后者是这个键不存在。通道
|
||||
之间按通道名字典序,通道内的顺序和贴进提示词的顺序一致。
|
||||
|
||||
**快照的键集合从来不是公共承诺**,所以这不是 `CLAUDE.md` §1.3 意义上的破坏性变更:下游换一个
|
||||
存储实现、改一个接缝的 `parameters()` 返回什么,快照照样会变、续跑照样报漂移,这本来就是这
|
||||
套设计的一部分。
|
||||
|
||||
### 动作执行接缝的契约补上「抛异常时会怎样」
|
||||
|
||||
`ActionExecutor` 的 docstring 原来只说了动作本身报错算「已执行」,实现方干脆不返回、直接抛出
|
||||
时会怎样一个字都没写,而库这一侧不捕获。现在正面写清三条:环境自己坏了(连不上、协议不对、
|
||||
会话没了)返回 `ActionStatus.ENV_ERROR`,不要以异常表达;真抛出来的异常库不捕获,原样穿出
|
||||
`run()` 与 `resume()`;`asyncio.CancelledError` 必须原样穿过。
|
||||
|
||||
**行为没有变,变的是它被写下来了。** 库不把执行器抛出的异常转成 `ENV_ERROR`,是因为那样会
|
||||
把适配器自己的 bug 伪装成环境故障送进下游的统计,而且要替这一步编一条步记录——那条记录会被
|
||||
恢复读成「上一步走完了」,把一个真正未知的状态抹成一个具体的值。异常抛出时日志停在「动作
|
||||
意图有、步记录无」,恢复照实把它读成状态未知。
|
||||
|
||||
把可预期的环境异常(连接超时、会话已关闭)捕获并返回 `ENV_ERROR` 是适配器的正常工作,不算
|
||||
`CLAUDE.md` §1.7 禁的那种吞错误——错误没有被吞,它变成了一个明确的状态值。
|
||||
|
||||
## 1.0.1(2026-08-11)
|
||||
|
||||
首个发布版本。十个模块全部落地,四层测试都在跑。
|
||||
|
||||
Reference in New Issue
Block a user