Files
mini-openclaw-v2/scripts/test_prescreen_panel.cjs
qiaoqichaoandClaude Code 2e7e19471c feat(v2): 纠偏第二批——接通历史经验加工闭环
覆盖 AC-50/51/52(BR-48/49、E-28)。审计确认的 P1-3 缺口"历史提炼与卡片
不在实时 API 调用图上"由此关闭:提炼、候选治理、卡片进方案全部有真实调用方。

后端:
- contracts:新增 E-28 EXTRACTION_UNAVAILABLE(运行级,不进 HTTP 信封)与
  KnowledgeExtraction 记录类型。契约层强制"失败不得带候选",
  AC-50「模型不可用⇒不产生候选」是类型约束而不是文档约定。
- services/experience_extraction.py(新):真实提炼入口。模型不可用或调用失败
  → 记 E-28 且 candidate_id 为空;不产半成品候选。传输纪律同 assistant_model
  (禁进程内代理、零重试),且 tools=[] 使"越权读材料"在结构上不可能。
  自然语言修订同样必须真实调用模型,没有"把中文猜成条件"的回退路径。
- services/knowledge_cards.py:新增 current_draft/revise/list_cards 与
  registry_conditions。修订只产草稿、保留 origin 与来源、升版本记 parent_version;
  test_cases 纳入可改字段——它是 component_evaluation 对自定义 Skill 的发布门禁,
  不开放则提炼出的候选永远卡在评测门外。
- services/project_assistant.py:enqueue 改用 review_plan.assemble(),
  让 T06-b 的依赖预检(E-19 可定位文案)与维度解析不再被绕过。
- api/prescreen.py:case-timeline、knowledge-extractions、knowledge-candidates
  (创建/修订/状态机)、review-plans(装配/状态机)。角色门禁与既有
  /components 入口一致;id 与数据域由服务端生成;按 id 的路由一律在 run() 内
  查归属,否则 KeyError 会变成 500 而不是 404。
- component_registry/review_plan:下沉 current_draft/version_key 与 list_plans。

前端:
- 新增 /experience-center 独立入口(BR-50)与 ExperienceCenter 组件;
  AgentNav 增加「历史经验加工」。
- originBadge 此前零调用方、.origin-* 无任何 CSS——守卫只 grep 库文件字符串,
  于是"测试通过但功能不存在"。本批补齐消费方与样式,并用左边框样式区分四种来源,
  保证去色仍可区分(AC-42/BR-48)。
- 守卫语义按 AC-55 调整:由"禁止新建路由"改为"禁止重复页面"
  (面板不得搬到独立路由、已合并页必须保持重定向),并新增
  experience-center-ui 守卫(已登记 verify.py,共 12 项检查)。

测试:新增 36 项(提炼 15 / 候选治理 15 / 卡片到运行 6),全量 unittest 无回归;
两次同项目运行对照证明卡片确实改变了核查结果,且 basis_ids 指向 卡片id@版本。

未闭合(记录在 17 的剩余缺口):真实模型提炼尚未实测(需端点);
界面未经浏览器点击验证;例外边界未进入可求值语言;规则类候选不在本批。

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

64 lines
3.7 KiB
JavaScript
Raw Permalink 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.
/**
* S1d 界面源码守卫。
*
* 这是**源码断言**,不是浏览器端到端测试(docs/v2/06-validation.md §2 已明确
* 现有 UI 脚本的性质)。它锁住的是可机械核对的约束:不新建路由、复用既有样式、
* 上传必须带批次标识、冲突裁定必须留痕、不得展示任何审批概率。
*/
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const root = path.resolve(__dirname, "..");
const read = (file) => fs.readFileSync(path.join(root, file), "utf8");
const panel = read("src/components/xingbang/ImportBatchPanel.tsx");
const workbench = read("src/app/xingbang-workbench/page.tsx");
const lib = read("src/lib/prescreen.ts");
// 1. 挂载在既有工作台内
assert.match(workbench, /import ImportBatchPanel from "@\/components\/xingbang\/ImportBatchPanel"/, "工作台应导入导入面板");
assert.match(workbench, /<ImportBatchPanel /, "工作台应渲染导入面板");
// AC-55:禁止的是**重复页面**(把面板搬到独立路由、把已合并页重新实现),不是新增路由本身。
for (const banned of ["prescreen", "import-batch", "material-import"]) {
assert.ok(!fs.existsSync(path.join(root, "src/app", banned)), `不得把面板搬到独立路由 src/app/${banned}`);
}
for (const merged of ["trajectory", "review-workbench", "report-workbench", "collateral-workbench"]) {
assert.match(read(`src/app/${merged}/page.tsx`), /redirect\(/,
`已合并页 src/app/${merged} 必须保持重定向,不得恢复成独立页面`);
}
// 2. 复用既有样式,不新增样式表
assert.ok(!/^import .*\.css";/m.test(panel), "面板应复用 workspace.css 既有 class,不新增样式表");
// 3. 上传必须带批次标识(否则服务端无法回写条目状态)
assert.match(panel, /form\.append\("batch_id", batch\.id\)/, "上传必须提交 batch_id");
assert.match(panel, /form\.append\("item_id", itemId\)/, "上传必须提交 item_id");
// 4. 冲突裁定必须显式选择 + 填写说明,且未填说明时按钮禁用
assert.match(panel, /resolution_note: note/, "裁定必须提交 resolution_note");
assert.match(panel, /selected_evidence_ids: \[evidenceId\]/, "裁定必须显式选择来源");
assert.match(panel, /disabled=\{busy \|\| !note\.trim\(\)\}/, "未填裁定说明时不得可提交");
// 5. 必须向用户说明"未确认不入事实"与"冲突不覆盖"(BR-01 / BR-23 的用户可见面)
assert.match(panel, /未上传或未确认的文件不会进入项目事实/, "必须说明未确认不入事实");
assert.match(panel, /不会按上传先后覆盖/, "必须说明冲突不按上传先后覆盖");
assert.match(panel, /不会自动挑一个/, "必须说明系统不会自动挑选冲突来源");
// 6. 不得展示任何审批概率(BR-12 / AC-12:未校准即无概率)
assert.ok(!/通过率|probability|概率环/.test(panel), "S1 面板不得展示任何审批概率");
assert.ok(!/通过率|probability/.test(lib), "S1 客户端不得引入概率字段");
// 7. 不得在前端编造判定结果:值必须来自服务端字段
assert.ok(!/value:\s*["']?\d/.test(panel), "面板不得硬编码业务数值");
// 8. 客户端必须同时吃下两种错误形状,且不丢既有 detail 的信息量
assert.match(lib, /data\?\.error/, "客户端必须能解包统一错误信封");
assert.match(lib, /JSON\.stringify\(detail\)/, "既有 detail 数组形态不得丢信息");
// 9. 空状态不得绘制虚构行(UI Spec §3)
assert.match(panel, /ra-empty/, "空批次应使用既有空状态样式");
assert.match(panel, /尚无导入批次/, "空批次应给出明确的空状态文案");
console.log("prescreen-panel-ui: PASS");