docs: split the release task at a human confirmation gate

Everything up to and including the full slow suite runs on the branch
without asking. Merging to main, pushing a tag, and uploading to the
registry cannot be taken back, and a registry version number cannot be
reused, so those wait for an explicit yes.

Also settles three things an executor would have tripped on: the
_AttemptUsage field is typed as the enum with .value applied only at
the recorder boundary, __all__ is not in strict alphabetical order, and
the port signature test freezes two params rather than the full list.
This commit is contained in:
2026-08-25 23:35:59 -04:00
parent 626bbdcc83
commit 85bcc23a6b
@@ -139,7 +139,7 @@ conda run -n PolyGateway lint-imports
更新 import`client.py``from polygateway.providers import get_capability, get_provider, resolve_thinking` 拆成两行)、`transports/openai_compat.py``client.py``TYPE_CHECKING` 块里 `ThinkingCapability` 的来源。
`__init__.py` 新增包根导出并加进 `__all__``__all__` 保持既有的字母序):`ThinkingCapability``ThinkingObservation``ThinkingUnsupportedError``get_capability``register_capability``resolve_thinking`
`__init__.py` 新增包根导出并加进 `__all__`该列表**不是严格字母序**——`DEFAULT_PROFILES` 现在就排在 `AllSourcesExhausted` 前面;沿用文件既有排列,把新符号插到同类符号附近即可):`ThinkingCapability``ThinkingObservation``ThinkingUnsupportedError``get_capability``register_capability``resolve_thinking`
`tests/unit/test_providers.py` 里针对被搬走符号的测试,整体移入 `tests/unit/test_thinking.py`
@@ -177,9 +177,9 @@ conda run -n PolyGateway python -c "from polygateway import ThinkingObservation,
`LLMResponse` 侧补 docstring`UNKNOWN` = 本次无信号判不出,**不是**"没推理";非流式路径下部分模型推理已计费却不回传正文(M3 实测 completion 53 vs 关闭档 3),该档即为 `UNKNOWN`
`transports/openai_compat.py` 的两条组装路径(流式 `_complete_stream` 460-475 行一带、非流式 `_complete_once` 543-560 行一带)在构造 `TransportResult` 时调 `observe_thinking(thinking=thinking, reasoning_tokens=...)` 填入。两条路径都要填——**只填一条正是 L5 要抓的那类分叉**。
`transports/openai_compat.py` 的两条组装路径(流式 `_complete_stream` 463-475 行、非流式 `_complete_once` 548-560 行)在构造 `TransportResult` 时调 `observe_thinking(thinking=thinking, reasoning_tokens=...)` 填入。两条路径都要填——**只填一条正是 L5 要抓的那类分叉**。
`middleware/retry.py` 组装 `LLMResponse` 处(约 377-392一带)透传 `thinking_observation=result.thinking_observation`
`middleware/retry.py` `_build_response`372-393 行)透传 `thinking_observation=result.thinking_observation`
### 测试要求(先失败后通过)
@@ -305,7 +305,7 @@ conda run -n PolyGateway pytest tests/unit/test_cache.py -v
**recorder**`sqlite.py` / `postgres.py``record_llm_call` 各加一参并接进取值元组,位置与 `COLUMNS` 严格同序。`sqlite.py:146` 的"24 字段冻结签名"改 25。
**emitter**`_AttemptUsage``thinking_observation: str = ThinkingObservation.UNKNOWN``of()` 从 response 取;三个 `emit_*` 各传一行(`emit_terminal_failure``ThinkingObservation.UNKNOWN`——无响应可言,默认值本身不撒谎);`_record` 签名增一参并下沉给 recorder。**所有新增字段只经 `_record` 这一个出口抵达 recorder,不新开调用点**(铁律:遥测调用点收敛为单一 helper,该出口已存在)。`middleware/telemetry.py:135` 的"组装 24 字段"改 25。
**emitter**`_AttemptUsage``thinking_observation: ThinkingObservation = ThinkingObservation.UNKNOWN`**内部字段用枚举类型**,裸 `str` 归一化只发生在下沉 recorder 那一步)`of()` 从 response 取;三个 `emit_*` 各传一行(`emit_terminal_failure``ThinkingObservation.UNKNOWN`——无响应可言,默认值本身不撒谎);`_record` 签名增一参并下沉给 recorder。**所有新增字段只经 `_record` 这一个出口抵达 recorder,不新开调用点**(铁律:遥测调用点收敛为单一 helper,该出口已存在)。`middleware/telemetry.py:135` 的"组装 24 字段"改 25。
**recorder 收到的必须是裸 `str`,不是枚举实例**`_AttemptUsage.thinking_observation` 内部用 `ThinkingObservation` 类型,但 `_record` 下沉给 recorder 时取 `.value``StrEnum` 虽是 `str` 子类,asyncpg 的参数编码对 `str` 子类不保证接受,而遥测写失败只会被降级成一条 warning——这类问题不会当场炸,只会让 Postgres 那一路悄悄少一列数据。归一化放在 emitter 侧,与 `tenant_id`/`meta`/`sampling` 由 emitter 定型后再交 recorder 是同一先例(`ports.py` docstring 明载该分工:recorder 只落库,不做语义判断)。
@@ -325,7 +325,7 @@ conda run -n PolyGateway pytest tests/unit/test_cache.py -v
### 测试要求(先失败后通过)
`tests/unit/test_ports.py`:现有 `TestTelemetryRecorderSignature` 的 parametrize 列表加入 `thinking_observation`,断言它存在、无默认值、是 KEYWORD_ONLY——改端口前必然红。
`tests/unit/test_ports.py`:现有 `TestTelemetryRecorderSignature` **并不冻结完整参数列表**——它只 parametrize 了 `["tenant_id", "meta"]` 两项,断言其无默认值且为 KEYWORD_ONLY。把 `thinking_observation` 加进该 parametrize 列表,断言同样三条——改端口前必然红。
`tests/unit/test_telemetry.py`:列数与列序断言(上表);新增一条 round-trip——记录一条 `thinking_observation=OBSERVED` 的调用后从 SQLite 读回该列等于 `"observed"`
@@ -415,7 +415,9 @@ conda run -n PolyGateway pytest tests/e2e/test_thinking_live.py -m slow -v
### 测试要求
`tests/unit/test_thinking.py` 既有的能力表用例覆盖(`evidence` 非空、`can_disable` 取值),无新增行为。本任务是事实更新,测试证据由 Task 7 的 e2e 真跑承担
**先失败后通过不适用于本任务,理由须写进提交信息**:本任务只改 `evidence` 字符串与注释,`can_disable` 取值不变,**没有行为变更**,因而没有可先失败的行为断言(`test-driven-development` 的结果门约束的是行为变更)。声明依然成立这一事实,其证据是 2026-08-25 的复测与 Task 7 的 e2e 真跑,不是本任务能自造的单测
`tests/unit/test_thinking.py` 既有的能力表用例(`evidence` 非空、`can_disable` 取值)须保持绿,作为回归证据。
### 验证
@@ -441,6 +443,8 @@ conda run -n PolyGateway pytest tests/unit/test_thinking.py -q
**`research-wiki/index.md`**:登记本 plan、design 与 finding。
**先失败后通过不适用于本任务**:纯文档同步,无行为变更。其验收是下方 grep 的可见输出——数字与模块名对不上就是没改完。
**`CHANGELOG.md`**:新增 1.3.1 条目。**断裂项置于条目最前**,沿用 1.3.0"请先读这一条"体例(设计 §13:版号既然不承担预警职责,预警由 CHANGELOG 独立扛)。三条必须显式列出——① `polygateway.providers` 的深路径 import 断裂(`ThinkingCapability` / `resolve_thinking` / `get_capability` / `register_capability` / `DEFAULT_CAPABILITIES` / `ThinkingUnsupportedError` 移入 `polygateway.thinking`,同时提升到包根,**推荐改用包根 import**);② `TelemetryRecorder.record_llm_call` 端口签名 24 参 → 25 参,自定义 recorder 实现须同步;③ M3 非流式开启推理时推理内容已计费却不回传,该档观测为 `UNKNOWN`,库现在会告警一次。
### Wiki 注册
@@ -471,13 +475,15 @@ grep -rn '22 字段' research-wiki/schemas/llm-calls.md # 预期无输出
版本号两处改 `1.3.1``pyproject.toml``__init__.py.__version__` 必须一致);`CHANGELOG.md` 的"未发布"定版为 `## 1.3.1(2026-08-25)`
按 CLAUDE.md §4.4.1 发布清单**逐步执行,不得跳步**:READMETask 9 已完成)→ CHANGELOG 定版 → 版本号两处 → 合并 main(`--no-ff`+ push → 打 tag 并 push → 构建 → 上传 registry → `pip download` 验证并解包确认新代码在内 → 建 Release + 挂仓库 + 核对包页面
本任务分两段,**中间是一道人类确认门**
合并前另需两道门:`verification-before-completion` 派全新上下文 verifier subagent 独立验证(跨 20+ 文件,属强制档);`requesting-code-review` 整分支审查。
**第一段:分支内可自主完成的验证**——CHANGELOG 定版为 `## 1.3.1(2026-08-25)`;版本号两处改 `1.3.1``verification-before-completion`**全新上下文** verifier subagent 独立验证(跨 20+ 文件,属强制档);`requesting-code-review` 整分支审查;在分支上跑 `make ci``pytest -m slow`(约 20-40 分钟——四个 e2e 文件与 Redis 时间语义变体默认被 `-m 'not slow'` 排除,不显式跑等于没跑)
合并到 main 后在 main 上重跑 `make lint` 与全套件,**外加 `pytest -m slow`**(约 20-40 分钟,四个 e2e 文件与 Redis 时间语义变体默认被 `-m 'not slow'` 排除,不显式跑等于没跑)
**人类确认门**:以上全绿后停下,把验证结果交给人类,**取得明确同意后**才执行第二段
关闭 issue #16 #17附修复说明与本次实测结论(诊断纠正 + 三层根因 + 落地形态)。
**第二段:外发且难以撤销的动作,一律等确认**——合并 main`--no-ff`+ push → 打 tag 并 push → 构建 → 上传 registry → `pip download` 验证并解包确认新代码在内 → 建 Release + 挂仓库 + 核对包页面 → 关闭 issue #16 / #17附修复说明(诊断纠正 + 三层根因 + 落地形态)。顺序按 CLAUDE.md §4.4.1**不得跳步**:包上传与 tag 一旦推出去就收不回,registry 里的版本号也不能复用。
合并到 main 后须在 main 上**重跑** `make lint` 与全套件外加 `pytest -m slow`——分支上跑过不算,合并本身可能引入差异。
### 验证