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
+3 -2
View File
@@ -147,7 +147,7 @@ TrajectoryStep 的输入/输出正文通过授权存储引用访问;cost 缺
| GET /runs/{id}/trajectory | 200 {run_id,steps:TrajectoryStep[],next_cursor};seq稳定分页 | 项目查看;404;按当前权限解析引用 |
| POST /projects/{project_id}/review-outcomes | meeting_id,occurred_at,reviewed_snapshot_id?,raw_code,conditions,evidence_ids,supersedes_id?;201 ReviewOutcome;服务端映射label | 指定评审记录人;E-03/04/07,E-23/26为已保存记录警示 |
| POST /projects/{project_id}/improvements | target,target_ref,source_run_ids,outcome_ids,change_summary,proposed_revision_ref;201 ImprovementCandidate draft | 提议者;E-03/04/07 |
| POST /improvements/{id}/transitions | action:evaluate/submit/approve/reject/release/withdraw,expected_revision,note;200 ImprovementCandidate | 审核与发布独立资格;E-03/05/24;release委托既有发布入口 |
| POST /improvements/{id}/transitions | action:evaluate/submit/approve/reject/release/withdraw,expected_revision,note;200 ImprovementCandidate | 审核与发布独立资格;E-03/05/24;release委托既有发布入口,**版本引用由服务端从真实发布结果产生,不接受客户端提交 `released_ref`**(提交即 422) |
提炼作业、数据生成与评测先做内部受控任务;不开放任意prompt/文件路径/SQL执行API。新增读取卡片、方案、会议结果和改进列表在T02沿既有组件目录/对象查询接口扩展;实际URL与所有前端消费字段须在Contract gate前补齐,当前明确为阻塞项,不由实现者临时猜路径。
@@ -161,7 +161,8 @@ TrajectoryStep 的输入/输出正文通过授权存储引用访问;cost 缺
| 会议结果 | 追加保存matched/unmatched;核对关联通过追加修订记录 | 无证据拒绝;E-23/26可保存但不计有效标签;更正不改原运行 |
| ImprovementCandidate | draft→evaluating→ready或rejected;ready→in_review→approved或rejected;approved→released;released→withdrawn | rejected可新建修订draft;评测失败保留原因;release引用已有不可变发布结果 |
改进发布与卡片发布同一事务/幂等命令边界,不允许一边标released一边没有对应版本;若复用服务跨事务,须在详细实现设计中明确outbox/恢复协议并有故障测试。评测锁定后才揭示结果;标签污染任何阶段均可追加作废记录。
改进发布与卡片发布同一事务/幂等命令边界,不允许一边标released一边没有对应版本;若复用服务跨事务,须在详细实现设计中明确outbox/恢复协议并有故障测试。
**已实现(纠偏第四批)**:`release` 由端点注入的 publisher 在不新增连接的同一个 `_db()` 事务内执行——先按候选的 `proposed_revision_ref` 以 `组件id@版本` **精确**定位草稿(不按组件 id 取"最新草稿",否则会发布与候选声明不符的版本),再调 `knowledge_cards.publish` → `component_registry.release`;publisher 抛异常即整体回滚,组件版本与候选状态要么一起生效、要么都不写。"发布失败保留旧版"与"自报版本号被拒"两条反例均有测试。评测锁定后才揭示结果;标签污染任何阶段均可追加作废记录。
存储优先现有对象/版本/审计机制,通过追加迁移保存新增类型;唯一键至少覆盖run+sequence、project+版本引用、命令幂等范围。完整DDL、索引、读写白名单及迁移前后恢复测试属于T02门禁,当前未建表。新数据根与旧库物理隔离由T01验证。
+45 -39
View File
@@ -105,53 +105,59 @@ docs/project-process/README.md、XINGBANG-ADAPTATION.md,执行 git status --sh
> 交付定义与四批顺序以 [16 交付纠偏规格](16-delivery-corrections.md) 为准;
> 下表的"已交付"只表示该任务包的代码与测试存在,不再表示产品功能可用。
- 日期:2026-10-01(**纠偏第三批实施轮**;第二批见 `2e7e194`/`01c88af`,第一批见更早提交)。
- 日期:2026-10-01(**纠偏第四批实施轮**;第三批见 `ae89462`/`3e15bda`,
第二批见 `2e7e194`/`01c88af`,第一批见更早提交)。
- 仓库:`F:/mini-OpenClaw-v2`;远程 `http://192.168.10.111:3000/qiaoqichao/mini-openclaw-v2.git`
(OQ-06 Q2 的远程归属已定案;端口/资源/CI 三问仍 open;**本轮未推送**)。
- 完成范围:**纠偏第三批「按原型完成界面」**——深色导航+浅色工作区、
三个模块的独立入口(AI 预审 / 历史经验加工 / 轨迹复盘)、轨迹回放界面。
`scripts/verify.py` **13 项检查**全通过(新增 `module-shell-ui` 源码守卫)。
- 完成范围:**纠偏第四批「接通评审反馈与改进发布」**——会议结果从页面录入并与材料版本对照;
改进候选接上**真实知识发布入口**,发布后新运行用新版本、旧运行仍可回放。
界面并入「轨迹复盘」模块(`/trajectory-center`),**不新增第四个导航入口**。
`scripts/verify.py` **14 项检查**全通过(新增 `improvement-panel-ui` 源码守卫)。
- 验证入口:`cd F:/mini-OpenClaw-v2 && backend/venv/Scripts/python.exe scripts/verify.py`;
本轮证据:`test-evidence/20261001-005027-ae89462/`(13/13 PASS)。
- 测试规模:全量 **567 项** unittest 无回归(第二批已含新增 36 项;本批为界面改动,
证据是源码守卫 + 生产构建,未新增后端单测)。
- 本轮提交:`ae89462` —— `feat(v2): 纠偏第三批——深色导航、三模块独立入口与轨迹复盘`。
- 卡片验收对照(详见 [17 四批交付记录](17-batch-delivery-records.md) 第三批):
- AC-53:`/` 呈现三个模块入口;导航为深色(`#102B27`/选中 `#244D41`),画布保持浅色;
原落地页内容保留在 `/assistant`,不再"只保留旧版布局"。
- AC-54:概率状态与依据、动态维度、推进沙盘在 `PrescreenPanel`;
可回放轨迹由新增 `TrajectoryPanel` 消费既有只读端点(失败保留、成本缺失留空、不渲染推理字段)。
- AC-55:新路由与样式通过守卫——已合并旧页仍为重定向、导航按角色裁剪、
模块以 `?project=` 为上下文且不硬编码项目 id、前后端路径一致。
- BR-50:三个模块各有独立路由与入口,均在**复用**既有组件而不是在新页里重写一份。
本轮证据:`test-evidence/20261001-013517-3e15bda/`(14/14 PASS)。
- 测试规模:全量 **572 项** unittest 无回归(新增 `test_improvement_release.py` 4 项 +
`test_improvements.py` 1 项)。
- 卡片验收对照(详见 [17 四批交付记录](17-batch-delivery-records.md) 第四批):
- **AC-57**:端点把 `_knowledge_publisher` 注入 `improvements.transition`;publisher 用候选声明的
`proposed_revision_ref` **精确**定位草稿(`组件id@版本`),调 `knowledge_cards.publish`
→ `component_registry.release`,写入不可变 `component_version` 并**覆盖 `release` 指针**。
端到端证明:同一方案发布前运行冻结 `卡片@1.0.0`,发布后**再跑一次**得到 `卡片@1.0.1`,
旧运行回放仍是 `1.0.0`。**不接受客户端自报版本号**(422);非知识类目标 409 诚实拒绝。
- **AC-56**:`TrajectoryPanel` 可追加会议结果(原始结果码、会议编号、材料版本快照、
更正走 `supersedes_id`);`matched` / `unmatched` 分组,未匹配单列且不计入分母。
- 未决项(阻塞后续):
- **F17(新)真实模型提炼未实测**:本机未设置 `XINGBANG_LOCAL_MODEL_*`,
- **F17** 真实模型提炼未实测:本机未设置 `XINGBANG_LOCAL_MODEL_*`,
AC-50 前半句目前只有"不可用路径 + 假传输成功路径"两类证据。
需端点后按 17 的操作步骤补一次真实调用;在此之前不得宣称"真实提炼已跑通"。
- **F18(新)例外边界未进入可求值条件语言**:注册表条件只支持数值阈值算子
(`present/absent` 被既有断言锁定拒绝),模型给出的例外保存为文本 `exception_note`。
- **F19(新)卡片适用条件不参与运行内逐项判定**:只用于域/范围/有效期过滤
(`knowledge_cards.effective()`);运行内条件求值需独立设计。
- **F18** 例外边界未进入可求值条件语言;**F19** 卡片适用条件不参与运行内逐项判定。
(同第二批,本批未动。)
- **F11** 界面仍未经浏览器验证:`/experience-center`、`/prescreen-workbench`、
`/trajectory-center` 与深色导航都只有"源码守卫 + 生产构建 + 代码走查"三层证据
(本机无 playwright/puppeteer/chromium)。深色底对比度已改为守卫内按 WCAG 实算
(最低 8.37:1),但配色达标 ≠ 实机版面已验收。
- **F20(新)新旧经验入口并存**:工作台仍保留旧「评审经验」页签(走
`assistant_experiences`),与第二批的知识卡片治理不是同一套。删除是用户可见的
破坏性动作,本批未擅自下线,需确认后处理。
- **F21(新)首页默认路由变更**:`/` 由聊天页改为业务工作区,原内容移到 `/assistant`。
若部署脚本、书签或外部链接依赖旧的 `/`,需另行确认。
- **F22(新)轨迹/会议结果的录入属第四批**:本批只做只读回放与只读对照。
`/trajectory-center`(本批新增的会议结果录入与改进面板也在内)都只有
"源码守卫 + 生产构建 + 代码走查"三层证据(本机无 playwright/puppeteer/chromium)。
本批另加了**真实服务器接口冒烟**(走界面同一串 HTTP 请求),但它同样不是浏览器点击。
深色底对比度已改为守卫内按 WCAG 实算(最低 8.37:1),配色达标 ≠ 实机版面已验收。
- **F20** 新旧经验入口并存(工作台旧「评审经验」页签);**F21** 首页默认路由已变更
`/` → `/assistant`。两者都需确认后处理。
- **F23(新)改进端点仍无角色门禁**:既有候选/方案端点对评测与发布有 `role()` 门禁,
改进端点只有项目范围检查。本批 release 接上真实发布后后果变重,但加门禁会改变既有
端点行为(现有 API 测试以 `manager` 身份调用),未擅自扩大改动。
"不自批"已由服务端强制(`must_not_self_approve` + `registry.release()` 的 `edited_by`)。
- **F24(新)对照样本是人工录入的 JSON**,非真实保留集;`evaluation.independent` 仍是
客户端声明的布尔——服务端无法自证独立性。
- **F25(新)会议结果是模拟录入**,未经真实会议系统;真实标签映射与身份权限待确认(M5/M6)。
- **F26(新)题库/编排无真实发布入口**(`question_profile`/`orchestration`):已按 409
诚实拒绝,非假接通。
- **F22 已关闭**(会议结果与轨迹的录入已在第四批实现)。
- **F15 已关闭**(改进发布已接真实知识发布入口,并经真实发布指针验证)。
- **F7** 各卡门禁仍 `proposed`,审阅者未指派——用户授权不等于评审通过。
- **F8** Docker 运行时冒烟未实测(本机 Docker Desktop 守护进程不可达)。
- **F6** OQ-06 Q3–Q5:端口冲突核验、运行资源、Gitea CI 是否启用仍 open。
- **F12/F14/F16** 仍有效;**F13** 已由第一批关闭(运行视图改由执行链出口产生)。
- **F15** 改进发布仍未接真实知识发布入口——属第四批。
- 下一任务:**第四批「评审反馈与改进发布」**(AC-56/57,含会议结果录入与
改进候选的真实知识发布接线);或先补 **F17**(需要你提供模型端点与调用时机)。
不要为了推进而把演示当验收。
- 待输入:模型端点与调用时机(F17);真实数据/制度/会议标签映射与客户身份权限(M5/M6);
Laya 硬件与中文效果需实机探测;Jev 端点与凭据。
- Git:本轮提交在 `codex/xingbang-v2-model-field-fix` 分支,**尚未推送**;
核对命令 `git log --oneline -3`(当前 HEAD `ae89462`)。
- **F12/F14/F16** 仍有效;**F13** 已由第一批关闭。
- 下一任务:四批纠偏已全部有交付(均记**部分完成**)。继续推进前先定:
补 **F17**(需要你提供模型端点与调用时机)或处理 **F11**(需要可用的浏览器自动化环境);
两者都需要你提供的输入,不要为了推进而把演示当验收。
- 待输入:模型端点与调用时机(F17);浏览器自动化环境(F11);真实数据/制度/会议标签映射与
客户身份权限(M5/M6);Laya 硬件与中文效果需实机探测;Jev 端点与凭据。
- Git:本轮改动在 `codex/xingbang-v2-model-field-fix` 分支,**尚未推送**;
核对命令 `git log --oneline -3`。
不得从旧恢复点 4d5a420 推断最新版本已备份。
+85 -1
View File
@@ -271,4 +271,88 @@ XINGBANG_LOCAL_MODEL_KEY=<你的 DeepSeek API Key>
## 第四批:接通评审反馈与改进发布
状态:**未开始**。
状态:**部分完成**。会议结果可从页面录入并与材料版本对照;改进候选走**真实知识发布入口**,
发布后新运行使用新版本、旧运行仍可回放——两条断言都由真实执行链(enqueue + worker)证明。
**未闭合**:界面仍未经浏览器点击/截图验证(同 F11);会议结果是**模拟录入**,未经真实会议系统。
覆盖:AC-56、AC-57、BR-51。
### 页面入口
| 入口 | 位置 |
|---|---|
| 轨迹复盘(既有入口,**不新增第四个**) | 深色导航「轨迹复盘」→ `/trajectory-center` |
| 会议结果录入与对照 | 同页 `TrajectoryPanel` 下半部「会议结果对照」→「追加会议结果」表单 |
| 改进对照与发布 | 同页 `ImprovementPanel`「改进对照与发布」 |
置于轨迹复盘之内而非新开模块:14 §7 本就把「轨迹、会议记录与反向改进」写在同一节,
AC-53 又要求恰好三个模块入口。`AgentNav.tsx` 未改动。
### 操作步骤(实现并按此核对)
1. **录入会议结果**:选项目 → `TrajectoryPanel` 底部填原始结果码、会议编号、可从
`project.runs[].input_snapshot_id` 选「对应的材料版本」→ 保存。
2. **更正**:在「更正哪条记录」里选旧记录 → 保存为**追加**并带 `supersedes_id`,历史不被覆盖。
3. **提改进候选**:`ImprovementPanel` ① 选来源运行/会议结果(必填其一)、改进目标、
要发布的草稿版本、变更说明 → 「提出改进候选」。
4. **两道门**:② 「对草稿做隔离评测」(发布门禁)与「提交旧/新对照」(候选门禁)是两件事,缺一不可。
5. **审核与发布**:提交审核 → 由**另一位**身份「通过审核」→「审核发布」。
发布请求体**只有 action**,界面不提交任何版本号。
### 实际结果
- **发布是真的发布**(本批核心):端点把 `_knowledge_publisher` 注入 `improvements.transition`,
publisher 用候选声明的 `proposed_revision_ref` **精确**定位草稿(`组件id@版本`,
不按组件 id 猜"最新草稿",否则会出现"审核的是 A、发布的是 B"),再调
`knowledge_cards.publish` → `component_registry.release`:自批拒绝、隔离评测门禁、
依赖可解析三道既有保证原样生效,写入不可变 `component_version` 并**覆盖 `release` 指针**。
- **不接受自报版本号**:release 请求带 `released_ref` 一律 422 `INVALID_REQUEST`,
候选状态不前进、不产生任何组件版本(反例已测)。
- **发布失败保留旧版**:未过隔离评测的草稿 → 409 `IMPROVEMENT_NOT_VALIDATED`,
`release` 指针仍指旧版本,候选仍是 `approved`。候选状态与组件版本写在**同一个 `_db()`
连接/事务**里,publisher 抛异常即整体回滚,不会出现"版本已发布、候选没标记"。
- **非知识类目标诚实拒绝**:`orchestration` / `question_profile` 无真实发布入口 →
409 并说明原因,不用"改状态"冒充发布。
- **AC-57 端到端**(`test_improvement_release.py`,真实 enqueue + `DurableWorker.process_next()`):
同一个已发布方案,发布前运行的 `component_versions` 含 `卡片@1.0.0`、
`basis_ids=[卡片@1.0.0]`;经改进候选发布 `1.0.1` 后**再跑一次**得到 `卡片@1.0.1`;
回放第一次运行仍是 `1.0.0`,其冻结版本未被改写(`replay.read_only=true`)。
- **AC-56**:会议结果按 `matched` / `unmatched` 分组;未匹配的单列并显示「不计入分母」,
与服务端 `denominator()` 一致(未送审、撤回、附条件、未知与未匹配都不进分母)。
- **真实服务器冒烟**(不是单测的替代,脚本 `scripts/xingbang_batch4_smoke.py`):
对运行中的 uvicorn 按界面同一串请求走通
建卡 → 隔离评测 → 发布 → 结构化修订 → 录入会议结果 → 提候选 → 对照 → 换身份审核
→ 发布(不带版本号);服务端返回 `released_ref=卡片@1.0.1`,且 `release` 指针实测指向 `1.0.1`。
只用了脚本新建的组件 id,未触碰既有组件的发布指针。
**副作用**:它在演示库(`backend/xingbang_data/project_assistant.sqlite3`)新建了若干
「冒烟:回款集中依赖核查」卡片与 `SMOKE-4` 会议结果;重复运行会累积。
这些是演示库里的可见脏数据,需要时可删——本记录如实列出,不做隐藏。
### 证据位置
| 内容 | 位置 |
|---|---|
| AC-57 端到端与四条反例(4 项) | `backend/test_improvement_release.py`(新增) |
| 自报版本号被拒(API 层) | `backend/test_improvements.py` `test_client_supplied_released_ref_is_rejected` |
| 界面源码守卫(新增) | `scripts/test_improvement_panel.cjs`(已登记 `scripts/verify.py`,共 **14** 项检查) |
| 轨迹面板守卫(改:区分「回放只读」与「结果只可追加」) | `scripts/test_module_shell_ui.cjs` |
| 全量验证 | `test-evidence/20261001-013517-3e15bda/`(`scripts/verify.py` **14/14 PASS**,含 `backend-unit` 全量 **572** 项) |
| 真实服务器冒烟 | `scripts/xingbang_batch4_smoke.py`(对 127.0.0.1:8012 实跑,退出码 0) |
### 剩余缺口
- **界面仍未经浏览器验证(F11 未变)**:本批新增的两个表面只有「源码守卫 + 生产构建
(`/trajectory-center` 3.87 → 8.86 kB)+ 真实服务器接口冒烟」三层证据,**没有浏览器点击或截图**;
表单交互、按钮、权限隐藏这些路径没有端到端证明。
- **会议结果是模拟录入**:AC-56 验的是"录入后与材料版本对照"的机制,不是真实会议系统对接;
真实标签映射与客户身份权限仍待确认(M5/M6)。
- **对照样本是人工录入的 JSON**,不是真实保留集;`evaluation.independent` 仍是**客户端声明**
的布尔(既有设计)——界面不把它包装成"已证明独立",但服务端也确实无法自证。
- **改进端点仍无角色门禁**:既有 `transition_knowledge_candidate` / `transition_review_plan` 对
评测与发布有 `role()` 门禁,改进端点(propose/evaluate/approve/release)只做项目范围检查。
本批给 release 接上真实发布后这个缺口的后果变重了,但加门禁会改变既有端点行为
(现有 API 测试以 `manager` 身份调用),**未擅自扩大改动**,留待确认。安全上真正关键的
"不自批"已由服务端强制(`must_not_self_approve` + `registry.release()` 的 `edited_by == actor`)。
- **题库/编排无真实发布入口**:已按 409 诚实拒绝,非假接通;需要时另行设计。
- **顺带发现、未修**:`backend/test_trajectory.py:173` 依赖 `recorded_at` 排序,两条记录落在
同一时钟刻度时顺序会翻转(本次全量跑中出现过一次,重跑即通过)。属既有测试脆弱性,非本批引入。