feat(v2): 纠偏第四批——会议结果录入与改进发布接真实知识发布入口

- api/prescreen.py:transition_improvement 注入 _knowledge_publisher,
  release 委托 knowledge_cards.publish → component_registry.release;
  按 proposed_revision_ref 以「组件id@版本」精确解析草稿(不取"最新草稿",
  否则会出现"审核的是 A、发布的是 B");客户端自报 released_ref 一律 422;
  非 knowledge 目标 409 诚实拒绝;发布与候选状态同一事务,失败保留旧版。
- TrajectoryPanel:新增会议结果录入(追加式更正走 supersedes_id;
  未匹配单列且不计入分母)。
- ImprovementPanel(新增):偏差→候选→两道评测门→独立审核→发布;
  请求体不含版本号,无自动上线,不自批。
- 界面并入「轨迹复盘」,不新增第四个导航入口(AC-53)。
- 守卫:新增 scripts/test_improvement_panel.cjs(verify.py 13→14 项);
  test_module_shell_ui.cjs 的「只读」断言改为分别锁死「回放只读」与「结果只可追加」。
- 测试:新增 backend/test_improvement_release.py(AC-57 端到端 + 三条反例);
  test_improvements.py 补「自报版本号被拒」。
- 文档:17 第四批交付记录、15 §7 交接、03 §9.4 发布事务边界。

验证:scripts/verify.py 14/14 PASS(backend-unit 572 项),证据
test-evidence/20261001-013517-3e15bda/;scripts/xingbang_batch4_smoke.py
对真实服务器实跑通过(发布指针实测移到新版本)。

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
2026-10-01 02:21:11 +08:00
co-authored by Claude Code
parent 3e15bda8cd
commit f775c6cbe4
13 changed files with 1269 additions and 91 deletions
+21 -2
View File
@@ -222,13 +222,32 @@ class ImprovementApiTests(unittest.TestCase):
candidate = created.json()
self.assertEqual(candidate["status"], "draft")
# release 不再接受调用方自报的版本号(BR-51 / AC-57);未过门禁在此先命中
blocked = self.client.post(
f"{self.base}/improvements/{candidate['id']}/transitions",
json={"action": "release",
"released_ref": {"id": "card-1", "version": "2.0.0"}}, headers=HEADERS)
json={"action": "release"}, headers=HEADERS)
self.assertEqual(blocked.status_code, 409)
self.assertEqual(blocked.json()["error"]["code"], "IMPROVEMENT_NOT_VALIDATED")
def test_client_supplied_released_ref_is_rejected(self):
"""发布版本必须由服务端从真实发布结果产生,不接受调用方提交。"""
created = self.client.post(f"{self.base}/projects/{PROJECT}/improvements",
json={"target_ref": {"id": "card-1", "version": "1.0.0"},
"proposed_revision_ref": {"id": "card-1", "version": "2.0.0"},
"change_summary": "扩适用范围",
"source_run_ids": ["run-1"]}, headers=HEADERS).json()
response = self.client.post(
f"{self.base}/improvements/{created['id']}/transitions",
json={"action": "release",
"released_ref": {"id": "card-1", "version": "2.0.0"}}, headers=HEADERS)
self.assertEqual(response.status_code, 422, response.text)
self.assertEqual(response.json()["error"]["code"], "INVALID_REQUEST")
# 自报的版本号不得让候选变成"已发布"
stored = self.client.get(f"{self.base}/projects/{PROJECT}/improvements",
headers=HEADERS).json()["items"][0]
self.assertEqual(stored["status"], "draft")
self.assertIsNone(stored["released_ref"])
def test_leakage_is_reported_as_e11(self):
created = self.client.post(f"{self.base}/projects/{PROJECT}/improvements",
json={"target_ref": {"id": "card-1", "version": "1.0.0"},