Files
mini-openclaw-v2/docs/v2/17-batch-delivery-records.md
T
qiaoqichaoandClaude Code 4720d72965 fix(v2): 浏览器端到端验证——修掉模块页在浏览器里渲染不出内容的缺陷
首次用真实 Chrome 打开 /trajectory-center(新增可选脚本 scripts/browser-e2e.cjs),
暴露 5 个源码守卫与生产构建都看不出的缺陷,全部修复:

1. 三个模块页读 `list.items`,而 /projects 返回的是 {projects, identity}——
   项目列表恒为空,prescreen-workbench / experience-center / trajectory-center
   整页渲染不出任何面板,只显示"当前身份没有可访问的项目"这种看着正常的空态。
   (第二/三批引入)改为 list.projects。
2. TrajectoryPanel 没被传 identity,canRecord 恒假 → 会议结果录入表单永不出现。
3. ImprovementPanel 草稿下拉以组件 id 作 key/value,同一组件多版本草稿时撞 React key,
   且选不准要发布哪一版;改以 `组件id@版本` 为键值。
4. JSX 文本里的 `**强调**` 会原样显示星号(JSX 不渲染 Markdown),共 9 处;
   改为 <b>(字符串上下文处直接去掉标记)。
5. 深色导航模块名被 flex 压缩折行("AI 预审"断成两行);
   加 flex:0 0 auto + white-space:nowrap。

另修一个既有测试脆弱点:test_trajectory.py 的更正用例断言"哪条排前面",
两条记录同刻度时排序翻转,曾让 verify.py 偶发失败;改为断言"两条都在、更正指向旧记录"。

回归防线:第 5 项补进 test_module_shell_ui.cjs 源码断言;
test_ui_readability.cjs 的导航字号断言改为不把 min-height 钉在规则首位。

验证:scripts/verify.py 14/14 PASS(backend-unit 572 项),
证据 test-evidence/20261001-030127-ff59d7e/;scripts/browser-e2e.cjs 19/19 PASS
(截图在 test-evidence/e2e-batch4/)。该 E2E 脚本刻意不接入 verify.py。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-01 03:04:18 +08:00

389 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 四批交付记录
口径见 [16 交付纠偏规格](16-delivery-corrections.md) §4:每批必须列出**页面入口、操作步骤、
实际结果、证据位置、剩余缺口**;未接通项标"部分完成"。
---
## 第一批:接通正常预审执行链
状态:**部分完成**(执行链已接通并经真实运行验收;模型类技能已能跑完全程,见文末
「更正记录」;界面点击本身尚未由自动化驱动,材料确认步骤未在浏览器中完整走一遍)。
### 页面入口
| 入口 | 位置 |
|---|---|
| 发起预审 | `/xingbang-workbench?project=DEMO-B`「材料与问题」页 → 顶部"开始AI预审"按钮 |
| 查看结果与依据 | 同页「预审与推进沙盘」面板:主审意见、核查结果、核查维度(10)、每条"查看依据" |
### 操作步骤(实际执行)
1. 启动 v2:后端 `uvicorn app:app --port 8012`(带 `XINGBANG_DEMO_ROLE_LOGIN=1`)、
前端 `npm run dev -- -p 3212`。
2. 经前端代理发起预审(与页面按钮同一请求):
`POST /api/risk-agent/project-assistant/projects/DEMO-B/runs`,角色头 `manager`。
3. 等 worker 执行结束(轮询运行状态)。
4. 读取 `GET .../prescreen/projects/DEMO-B/runs/{run_id}`。
5. 浏览器打开工作台并渲染面板截图。
### 实际结果
- 运行结束状态 `partial`,视图 `recommendation=supplement`,`probability=null`。
- **维度 10 个**:5 已核查 / 2 待核实 / 3 存在阻断项;阶段 1–2
(`corporate.consistency`、`corporate.cashflow`、`corporate.assets`、`corporate.coverage`、
`skill-quality`、`skill-finance`、`skill-credit`、`demo.coverage`、`rules-corporate`、`corporate.analysis`)。
- **核查结果 4 条**(`lease.coverage.below_demo_threshold`、`lease.asset.duplicate`、
`financial.cashflow.missing`、`financial.liabilities.conflict`),各带依据与组件版本。
- **证据 14 条**随视图返回,界面"查看依据"可展开来源与定位。
- **未完成项 1 条**:`RUN_STEP_FAILED 金租对公业务综合核查:未配置受信模型,语义核查未执行`
——模型不可用时如实标未完成,不伪造结论。
- 缺证 2 项:`financial.operating_cashflow`、存在尚未人工确认的材料候选。
- 反复发起产生多条记录(本轮 3 条),旧记录仍可读;轨迹引用指向本次运行。
- 呈现判定:10 维超出雷达可读范围 → 界面改用分组列表并保留全部维度(AC-44 真实分支)。
### 证据位置
| 内容 | 位置 |
|---|---|
| 会话内验证日志 | 本轮运行输出(发起→轮询→读视图) |
| 端到端测试 | `backend/test_prescreen_from_run.py`(12 项,真实 enqueue + worker 执行) |
| 界面渲染 | `claude_use/tmp/v2-batch1-panel.png`(本机截图,未入库) |
| 全量验证 | `test-evidence/20260930-123451-0228966/`(`scripts/verify.py` 11/11 PASS,531 项单测) |
| 推送状态 | 已推送 `origin/main`:`f118721..c5edb40`;`git ls-remote origin main` 实测与本地 HEAD 一致 |
### 剩余缺口
- **界面点击未由自动化驱动**:本机无 playwright/puppeteer/chromium-cli,发起预审是通过
与页面按钮同一条代理请求完成的;"点按钮"这一步没有脚本化证据。
- **材料确认已走通(部分闭合)**:通过应用自己的入口补齐演示项目缺的字段——
`POST /documents` 上传按解析器键名书写的材料(`operating_cashflow: 1200`)→
项目待确认候选 → `POST /supplements` 带 `candidate_ids` 确认 →
`financial.operating_cashflow` 等 3 个字段生效(revision 5→6)。
**未闭合部分**:仍是接口调用而非浏览器点击。
- **演示角色需要 `XINGBANG_DEMO_ROLE_LOGIN=1`**:否则 worker 的 `authorized()` 会把
演示角色发起的运行判为"权限已撤销"(运行会失败但可读)。这是配置要求,需写入部署说明。
- **模型已接通并能跑完全程(2026-09-30 第二轮更正,见下方「更正记录」)**:配置 DeepSeek
(`XINGBANG_LOCAL_MODEL_*`)后,模型类技能 `corporate.analysis` 真实发起调用。
本轮首测仍报 `模型引用未知字段`——但**根因不是缺 `financial.operating_cashflow`**;
修复字段表三处缺陷后,模型跑完全程:工具调用 11 次 / 预算 18,降级记录为空。
- 取消中的运行不产出视图(只处理 completed/partial/failed)。
### 更正记录(2026-09-30 第二轮实测)
**更正**:本节原先记为"模型已接通,但预算不足",其归因有误。第二轮用真实模型复测后确认:
1. **`模型引用未知字段` 与缺 `financial.operating_cashflow` 无关。** "已定义但本次无值"的
字段,键仍然存在(`backend/services/assistant_materials.py` 的 `apply_candidate` 无条件建键),
不会触发该守卫;补这个字段因此不可能修好它。实测补件后(revision 2→4,缺证清零)报错照旧。
2. **真正被拒的是 `repayment.*`,进而是字段表的三处缺陷**:
- `backend/skills/financial-leasing-*/SKILL.md` 的 `inputs` 声明了 8 个 S1 标准字段表里
**不存在**的字段:还款来源、涉诉、增信、保证金、保险、客户名称;
- 字段守卫比对的是"当前恰好有值的字段",而非标准字段表,于是把"标准字段但本次无数据"
误判为未知字段——与提示词自身"缺失资料标 unknown"**自相矛盾**;
- 模型从未被告知允许使用的字段名,只能靠猜(实测先猜 `repayment.annual_amount`,
放开后改猜 `metrics.debt_ratio`)。
3. **工具预算不是约束**:跑通的那次实际只用了 **11 次**,原值 12 本就够用。
`max_tool_calls` 12→18、超时 120s→180s 保留作余量,但**不构成修复**。
**已修复(4 个文件)**:
| 文件 | 改动 |
|---|---|
| `backend/services/assistant_materials.py` | 补 8 个标准字段;`NUMERIC` 排除两个非数值字段;`legal.case_count` 加非负整数校验 |
| `backend/services/assistant_model.py` | 守卫改为比对标准字段表;向模型下发 `standard_fields`;提示词加字段约束;删除遮蔽模块级导入的局部导入 |
| `backend/services/project_assistant.py` | 补 `insurance.`→租赁物、`deposit.`→增信 维度前缀 |
| `backend/services/component_registry.py` | 方案预算与版本(`1.0.0`→`1.1.0`) |
字段表三处是代码层改动,**对既有数据库立即生效**;预算存放在冻结组件里,需走
`POST /components` 草稿 → publish 才对既有库生效。
**实测结果**(一次性临时库 + DeepSeek,演示库未改动):`corporate.analysis` 由 `failed`
变为 `partial`(部分结果为 `unknown`,属如实标注而非失败),降级记录为空,
核查结果 3 条 → 14 条。证据:`test-evidence/20260930-171301-962d425/`
(`scripts/verify.py` 11/11 PASS,531 项单测)。
**本轮新发现、尚未修复**:`skill-quality` 的完整性检查只遍历"已存在值的字段"
(`backend/services/skill_executor.py`),因此**从未提供过的字段永远不会被判为缺失**——
真实项目下"资料完整性"会漏报。建议单独立项,不并入本批。
---
## 运行环境配置(本地演示,2026-09-30 实测)
复现本记录需要以下环境变量。**密钥一律不入库、不写入任何文件**:本地演示用 shell 变量
展开传入,正式环境请用部署系统的密钥管理。
```bash
XINGBANG_DEMO_ROLE_LOGIN=1 # 演示角色发起运行必需,否则 worker 判"权限已撤销"
XINGBANG_LOCAL_MODEL_URL=https://api.deepseek.com/v1
XINGBANG_LOCAL_MODEL_NAME=deepseek-flash # 可换 deepseek-v4-pro(语义核查质量优先)
XINGBANG_LOCAL_MODEL_KEY=<你的 DeepSeek API Key>
# 后端启动前清空代理:实测直连 api.deepseek.com 可用(HTTP 200),
# 系统代理 127.0.0.1:7890 不可用(HTTP 000,与 AGENTS.md 记录的坑一致)
```
启动:`cd backend && venv/Scripts/python.exe -m uvicorn app:app --host 127.0.0.1 --port 8012`;
前端:`npm run dev -- -p 3212`,并设 `RISK_AGENT_BACKEND_URL=http://127.0.0.1:8012`(v2 隔离端口)。
---
## 第二批:完成历史经验加工流程
状态:**部分完成**。后端闭环(真实提炼入口 → 候选治理 → 卡片进方案并影响运行)已接通,
并经真实执行链测试;本模块页面入口已建并通过生产构建与源码守卫。
**未闭合**:① 真实模型提炼调用尚未实测(需要模型端点,见"剩余缺口");
② 界面点击未由自动化驱动(本机无浏览器自动化,沿用第一批 F11 口径)。
覆盖:AC-50/51/52、BR-48/49、E-28;页面入口部分覆盖 BR-50(三个入口中的第一个)。
### 页面入口
| 入口 | 位置 |
|---|---|
| 历史经验加工 | 顶部导航「历史经验加工」→ `/experience-center?project=<项目>` |
| 流程四段 | ① 来源:历史过程时间线 → ② 提炼:用真实模型生成候选 → ③ 候选:修订/评测/发布 → ④ 方案:让卡片影响核查 |
### 操作步骤(实现并按此验收)
1. 「从项目材料重建时间线」→ `POST /prescreen/projects/{id}/case-timeline`(确定性,不调用模型)。
2. 勾选来源事件、填写提炼要求 →「请求提炼」→ `POST .../knowledge-extractions`。
3. 候选卡上「自然语言修改适用范围」或「补充独立测试样本」→ `PATCH .../knowledge-candidates/{card_id}`。
4. 「隔离评测」→ 由**另一位**有发布资格的人「审核发布」→ `POST .../knowledge-candidates/{card_id}/transitions`。
5. 已发布卡「选入新方案」→「评测方案」→「发布方案」→ `POST .../review-plans` 与 `/review-plans/{id}/transitions`。
6. 在项目工作台用该方案发起预审;运行结果冻结并记录卡片版本。
### 实际结果
- **提炼(模型未配置)**:记录 `status=failed`,`candidate_id=null`,degradations 带
`EXTRACTION_UNAVAILABLE`,文案"本次未能完成经验提炼,请稍后重试或改为人工创建";
**提炼跑完后库里一张候选草稿都没有**(`test_experience_extraction` 的反例断言)。
这与第一批"模型不可用如实标未完成"同口径。
- **提炼(模型可用)**:以假传输层走真实 `agent_manager.invoke_scoped` 路径,
产出 `origin=history_derived` 且带来源事件 id 的 Skill 候选;记录保存
`prompt_version=history-extraction.v1` 与模型名,可核对"确实发起了调用"。
**真实模型调用未实测**(见剩余缺口)。
- **候选治理**:自然语言修订真的发起一次模型调用(测试断言调用次数为 1);
模型不可用时**拒绝且卡片一字未改**(比对修订前后 revision 与 conditions);
修订后仍为 `history_derived` 且保留来源、状态仍为 draft;已发布后修订升版本并记 `parent_version`。
- **卡片影响核查**:同项目两次运行对照——基座方案 `corporate.pre-review` 不含该卡片,
运行里没有该核查项;含卡片的新方案运行里出现 1 条 `history.repayment.dependency`,
其 `basis_ids=[skill.history.repayment@1.0.0]`,`component_versions` 含 `(卡片id, 1.0.0)`。
- **依赖门禁**:草稿卡片进方案时,发布被 409 拒绝;引用不存在组件时评测 `passed=false` 且发布被拒。
### 证据位置
| 内容 | 位置 |
|---|---|
| 提炼入口(15 项) | `backend/test_experience_extraction.py` |
| 候选治理与自然语言修订(15 项) | `backend/test_knowledge_governance.py` |
| 卡片 → 方案 → 运行(6 项) | `backend/test_card_to_run.py` |
| 界面源码守卫(新增) | `scripts/test_experience_center_ui.cjs`(已登记进 `scripts/verify.py`) |
| 全量验证 | `test-evidence/20261001-003148-2e7e194/`(`scripts/verify.py` **12/12 PASS**,含 `backend-unit` 全量 567 项、`frontend-build`、新增 `experience-center-ui` 守卫) |
### 剩余缺口
- **真实模型提炼未实测(P0)**:AC-50 的"若模型已配置则实际调用"需要一次真实调用证据。
本轮机器上没有设置 `XINGBANG_LOCAL_MODEL_*`,因此只交付了"不可用路径"与"假传输成功路径"。
实测方法:带 `XINGBANG_DEMO_ROLE_LOGIN=1` 与三个模型变量启动后端,再走上面步骤 2;
在此之前不得宣称"真实提炼已跑通"。
- **界面点击未由自动化驱动**:本机无 playwright/puppeteer/chromium-cli。界面证据是
"源码守卫 + 生产构建(`/experience-center` 6.08 kB)+ 代码走查",**不是**端到端点击证明;
本批的"页面入口/操作步骤"是按实现与接口核对写出的,不是浏览器截图。
- **例外边界未进入可求值条件语言**:注册表条件只支持数值阈值算子,`present/absent` 被既有断言
锁定拒绝,因此模型给出的例外保存为文本 `exception_note`,供专家修订,不作为可执行条件。
- **卡片适用条件不参与运行内逐项判定**:`conditions` 目前只用于域/范围/有效期过滤
(`knowledge_cards.effective()`);运行内条件求值需独立设计,不在本批冒充完成。
- **治理状态只实现 draft/published**:`in_review`/`rejected`/`withdrawn` 在注册表没有落地表达,
候选状态机因此只提供 `evaluate` 与 `publish`,不造一个假状态掩盖。
- **规则类(Rule)候选不在本批**:数值阈值与正式依据门禁(`_check_hard_basis`)需要单独设计,
本批只产出 Skill 类候选(可验证的缺件核查动作)。
- **顺带发现、未修**:`01-requirements.md` 的 E-18 写作 `HARD_RULE_BASIS_REQUIRED`,
但代码走的是 `ValueError` → 422 `INVALID_REQUEST`,错误码未对齐(既有问题,非本批引入)。
## 第三批:按已确认原型完成新版界面
状态:**部分完成**。深色导航、三个模块的独立入口、轨迹回放界面已接通并通过
生产构建与源码守卫。**未闭合**:界面仍未经浏览器点击/截图验证(同 F11,本机无浏览器自动化);
轨迹与会议结果的**录入**属第四批;工作台旧的「评审经验」页签未下线。
覆盖:AC-53/54/55、BR-50。
### 页面入口
| 入口 | 位置 |
|---|---|
| 业务工作区首页 | `/`(三个模块入口卡片;`?project=` 携带项目上下文) |
| AI 预审 | 深色导航「AI 预审」→ `/prescreen-workbench` |
| 历史经验加工 | 深色导航「历史经验加工」→ `/experience-center`(第二批交付) |
| 轨迹复盘 | 深色导航「轨迹复盘」→ `/trajectory-center` |
| 原落地页内容 | `/assistant`(保留,不再是默认首页) |
### 操作步骤(实现并按此核对)
1. 打开 `/` → 看到三个模块入口卡片与"当前项目上下文"提示。
2. 进入任一模块 → 深色导航高亮当前模块;`?project=` 在模块之间保持。
3. AI 预审:选项目 → 选方案 → 填本次要求 → 发起预审 → 下方结果面板给出
概率状态与依据、动态维度、推进沙盘、每条"查看依据"。
4. 轨迹复盘:选运行 → 步骤流(序号 / 类型 / `组件@版本` / 状态 / 错误码 / 证据数 / 成本 / 耗时)
→ 加载更多 → 会议结果只读对照。
### 实际结果
- 导航为深色(`nav.bg #102B27`、选中 `#244D41`、`muted #B1C6BE`),工作区画布保持浅色
`#f5f7f9`;导航内的身份选择器改用深色变体,避免深底白字不可读。
- 三个模块各有独立路由并各自渲染**复用**的组件(`PrescreenPanel` / `ImportBatchPanel` /
`ExperienceCenter` / `TrajectoryPanel`),路由里没有再写一份实现。
- 已合并的旧页 `/trajectory`、`/review-workbench`、`/report-workbench`、`/collateral-workbench`
**仍是重定向**,没有被复活(AC-55 的防重复页面)。
- 轨迹面板:失败/跳过步骤照常列出并带错误码;成本缺失显示"未记录(不填 0)";
展示服务端"不包含模型内部推理过程"的说明;无任何写操作。
- 生产构建产物(实测):`/` 1.98 kB、`/prescreen-workbench` 2.36 kB、
`/trajectory-center` 3.87 kB、`/experience-center` 7.48 kB、`/assistant` 6.08 kB。
- 首页入口卡片沿用了既有的 `.xb-project-card`,但该类此前只用于 `<button>`;
作为 `<a>` 会带上浏览器默认下划线,因此补 `display:block;text-decoration:none`
(只影响该类,按钮用法不受影响)。
- 现有 7 项界面守卫全部继续通过(可读性、S1 面板、预审面板、加工中心、评审工作台、身份恢复、助手访问)。
### 证据位置
| 内容 | 位置 |
|---|---|
| 模块壳与三入口源码守卫(新增) | `scripts/test_module_shell_ui.cjs`(已登记 `scripts/verify.py`,共 **13** 项检查) |
| 全量验证 | `test-evidence/20261001-005027-ae89462/`(`scripts/verify.py` **13/13 PASS**,含 `backend-unit` 全量 567 项、`frontend-build`、新增 `module-shell-ui` 守卫) |
### 剩余缺口
- **界面仍未经浏览器验证(F11 未变)**:深色导航与三个模块都只有"源码守卫 + 生产构建 + 代码走查"
三层证据,**没有浏览器点击或截图**;原型本身也没有视觉门禁(04-ui-spec §7 已声明不以原型替代验收)。
深色导航的文字对比度已改为**机械断言**(守卫内按 WCAG 公式实算,阈值 AA 4.5:1):
未选中 12.03:1、选中 9.49:1、muted 8.37:1、品牌 13.91:1,全部达标——但这证明的是配色可用,
不等于版面在实机上的观感已验收。
- **轨迹与会议结果的录入属第四批**:本批只做只读回放与只读对照,界面上写明了这一点。
- **工作台仍保留旧「评审经验」页签**(走 `assistant_experiences`,与第二批的知识卡片治理不是同一套)。
新旧并存会造成"哪里才是正门"的困惑;但删除是用户可见的破坏性动作,本批不擅自下线,留待确认。
- **`AgentLoopFlow.tsx`(`src/components/trajectory/`)仍是零引用死代码**:本批没有复用它,
也没有删除——它不挡事,清理不属于本批交付范围。
- **原型顶部的演示提示条未实现**:原型有"原型演示 · 未连接真实数据与模型"横幅;
本批只做了导航与工作区骨架,未加该横幅(各模块内部已有等价的域与来源标识)。
- **首页默认路由发生变化**:`/` 由聊天页改为业务工作区,原内容移到 `/assistant`。
这是**用户可见的行为变化**;若部署脚本、书签或外部链接依赖旧的 `/`,需另行确认。
## 第四批:接通评审反馈与改进发布
状态:**部分完成**(较本轮初版有实质性推进)。会议结果可从页面录入并与材料版本对照;
改进候选走**真实知识发布入口**,发布后新运行使用新版本、旧运行仍可回放——两条断言都由
真实执行链(enqueue + worker)证明。**本批首次做了真实浏览器端到端验证(F11 对这两个表面已关闭)**,
并因此发现并修掉 5 个源码守卫与生产构建都看不出的缺陷(见下「浏览器验证发现的问题」)。
**未闭合**:会议结果是**模拟录入**,未经真实会议系统;`/experience-center` 与 `/prescreen-workbench`
虽在同一次浏览器验证中被顺带修好,但其自身交互仍未逐项点过。
覆盖:AC-56、AC-57、BR-51;并顺带修复 AC-53/AC-55 的运行时缺陷。
### 页面入口
| 入口 | 位置 |
|---|---|
| 轨迹复盘(既有入口,**不新增第四个**) | 深色导航「轨迹复盘」→ `/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-030127-ff59d7e/`(`scripts/verify.py` **14/14 PASS**,含 `backend-unit` 全量 **572** 项) |
| 真实服务器冒烟 | `scripts/xingbang_batch4_smoke.py`(对 127.0.0.1:8012 实跑,退出码 0) |
| **真实浏览器端到端** | `scripts/browser-e2e.cjs`(系统 Chrome;**19/19 PASS**);截图与 `result.json` 在 `test-evidence/e2e-batch4/` |
### 浏览器验证发现的问题(源码守卫与生产构建都查不出来)
首次用真实 Chrome 打开 `/trajectory-center` 后暴露的缺陷。「看起来正常的空态」是它们的共同点——
`/projects` 读错了字段,页面安静地显示"当前身份没有可访问的项目",构建与守卫全都通过。
| # | 问题 | 归属 | 处理 |
|---|---|---|---|
| 1 | 三个模块页读 `list.items`,而 `/projects` 返回的是 `{projects, identity}` → 项目列表恒为空,**整个模块页渲染不出任何面板** | 第二/三批引入(prescreen-workbench、experience-center、trajectory-center 三处) | 改为 `list.projects` |
| 2 | `TrajectoryPanel` 没被传 `identity`,`canRecord` 恒假 → **会议结果录入表单永远不出现** | 本批 | 传入 `identity` |
| 3 | `ImprovementPanel` 的草稿下拉以组件 id 作 key/value,同一组件有多个版本草稿时**撞 React key**,也选不准要发布哪一版 | 本批 | 改以 `组件id@版本` 为键值 |
| 4 | JSX 文本里写 `**强调**` 会**原样显示星号**(JSX 不渲染 Markdown),共 9 处 | 第一/二批 5 处、本批 4 处 | 改为 `<b>`(字符串上下文处直接去掉标记) |
| 5 | 深色导航的模块名被 flex 压缩**折行**("AI 预审"断成两行),三个入口读不清 | 第三批 | `flex:0 0 auto; white-space:nowrap` |
此外还修掉一个**既有测试脆弱点**:`test_trajectory.py` 的更正用例断言"哪条排前面",
而两条记录可能落在同一时钟刻度、排序随之翻转,曾让 `scripts/verify.py` 偶发失败。
改为断言"两条都在、更正指向旧记录",与排序无关;连跑 5 次稳定通过。
回归防线:第 5 项已补进 `scripts/test_module_shell_ui.cjs` 的源码断言;
`scripts/test_ui_readability.cjs` 的导航字号断言改为不把 `min-height` 钉在规则首位
(否则它会因新增属性而误报)。第 1–4 项属于"只有浏览器能看见"的一类,
目前只由 `scripts/browser-e2e.cjs` 覆盖——该脚本**刻意不接入 verify.py**。
### 剩余缺口
- **F11 对本批两个表面已关闭,但未全面关闭**:`/trajectory-center` 已由 `scripts/browser-e2e.cjs`
在真实 Chrome 里走过(19/19),有截图;`/experience-center` 与 `/prescreen-workbench`
只验证到"能被本次路由改动修好、能渲染",其自身交互没有逐项点过,**仍记 F11 未闭合**。
另外该脚本**不在 `verify.py` 里**,也就是说这条证据不会随强制门禁自动重跑——
它需要本机 Chrome 与一次性 `npm i --no-save puppeteer-core`。
- **浏览器验证只覆盖到"能走通"这一层**:没有做视觉回归(无基线截图比对)、没有多分辨率、
没有键盘可达性与屏幕阅读器检查。深色导航对比度仍是守卫内按 WCAG 实算,不是实测。
- **会议结果是模拟录入**: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 诚实拒绝,非假接通;需要时另行设计。
- **浏览器 E2E 尚未固化为项目门禁**:是否把它接进 `verify.py`、是否把 `puppeteer-core`
写进 `package.json`,是产品/工程决策,本批**没有替用户决定**,只提供可选脚本。