100 lines
6.5 KiB
JavaScript
100 lines
6.5 KiB
JavaScript
/**
|
||||
|
|
* 改进对照与发布 界面源码守卫(纠偏第四批 · BR-51 / AC-56 / AC-57)。
|
|||
|
|
*
|
|||
|
|
* 这是**源码断言,不是浏览器端到端测试**(docs/v2/06-validation.md §2)。
|
|||
|
|
* 它专防审计确认过的偏差模式:「函数在、功能不在」——面板看着有按钮,
|
|||
|
|
* 实际没接真实端点,或者把版本号从界面自报上去冒充发布。
|
|||
|
|
*/
|
|||
|
|
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 exists = (file) => fs.existsSync(path.join(root, file));
|
|||
|
|
|
|||
|
|
const panel = read("src/components/xingbang/ImprovementPanel.tsx");
|
|||
|
|
const page = read("src/app/trajectory-center/page.tsx");
|
|||
|
|
const trajectory = read("src/components/xingbang/TrajectoryPanel.tsx");
|
|||
|
|
const prescreenApi = read("backend/api/prescreen.py");
|
|||
|
|
|
|||
|
|
// ---------------------------------------------------------------- 1. 入口与挂载
|
|||
|
|
assert.ok(exists("src/components/xingbang/ImprovementPanel.tsx"), "应有改进对照面板组件");
|
|||
|
|
assert.match(page, /import ImprovementPanel from "@\/components\/xingbang\/ImprovementPanel"/,
|
|||
|
|
"轨迹复盘路由应复用改进面板组件,而不是在路由里再写一份");
|
|||
|
|
assert.match(page, /<ImprovementPanel/, "轨迹复盘路由必须真的挂载改进面板");
|
|||
|
|
assert.match(page, /<TrajectoryPanel/, "轨迹复盘路由应仍挂载轨迹面板");
|
|||
|
|
|
|||
|
|
// ---------------------------------------------------------------- 2. 不新增第四个入口(AC-53/BR-50)
|
|||
|
|
const nav = read("src/components/xingbang/AgentNav.tsx");
|
|||
|
|
assert.ok(!nav.includes("/improvement"), "改进模块必须并入轨迹复盘,不得新增导航入口");
|
|||
|
|
for (const banned of ["improvement", "improvements"]) {
|
|||
|
|
assert.ok(!exists(`src/app/${banned}`), `不得新建第四个模块路由 src/app/${banned}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ---------------------------------------------------------------- 3. 端点真的接上了
|
|||
|
|
for (const call of [
|
|||
|
|
/`\$\{PRESCREEN\}\$\{path\}\/improvements`/, // 项目级列表与提出候选
|
|||
|
|
/`\$\{PRESCREEN\}\/improvements\/\$\{encodeURIComponent\(item\.id\)\}\/transitions`/,
|
|||
|
|
/knowledge-candidates/, // 选草稿 + 草稿隔离评测
|
|||
|
|
/review-outcomes/, // 偏差来源:会议结果
|
|||
|
|
]) {
|
|||
|
|
assert.match(panel, call, `改进面板必须真的调用既有端点:${call}`);
|
|||
|
|
}
|
|||
|
|
assert.match(panel, /action: "evaluate"|action,\s*\.\.\.extra/, "必须能提交对照评测");
|
|||
|
|
assert.match(panel, /action: "release"|transition\(item, "release"\)/, "必须提供发布动作");
|
|||
|
|
|
|||
|
|
// ---------------------------------------------------------------- 4. 版本号不得由界面提交(BR-51 的核心)
|
|||
|
|
assert.ok(!/released_ref\s*:/.test(panel),
|
|||
|
|
"界面不得提交 released_ref:发布版本必须由服务端从真实发布结果产生");
|
|||
|
|
assert.ok(!/released_ref\s*:/.test(trajectory), "会议结果录入不得提交 released_ref");
|
|||
|
|
// 展示时只能读服务端返回的引用
|
|||
|
|
assert.match(panel, /item\.released_ref/, "发布结果应展示服务端返回的版本引用");
|
|||
|
|
|
|||
|
|
// ---------------------------------------------------------------- 5. 不提供自动上线
|
|||
|
|
const buttonTexts = [...panel.matchAll(/<button[^>]*>([\s\S]*?)<\/button>/g)].map((m) => m[1]);
|
|||
|
|
assert.ok(!buttonTexts.some((text) => /自动上线|一键|自动发布/.test(text)),
|
|||
|
|
"不得提供自动上线/一键发布按钮");
|
|||
|
|
assert.match(panel, /没有"自动上线"|不提供自动上线/, "界面必须写明不提供自动上线");
|
|||
|
|
assert.ok(!/enqueue|\/runs`/.test(panel), "发布后不得自动发起运行");
|
|||
|
|
|
|||
|
|
// ---------------------------------------------------------------- 6. 不自批
|
|||
|
|
assert.match(panel, /proposed_by !== identity\?\.id/, "提出者不得看到自己的通过审核按钮");
|
|||
|
|
assert.match(panel, /独立审核|不能由提出者|审核需要独立/, "界面必须说明审核需要独立的人");
|
|||
|
|
assert.match(panel, /release_blockers|blockersOf|未完成门禁/, "必须把未完成门禁展示出来(E-24)");
|
|||
|
|
|
|||
|
|
// ---------------------------------------------------------------- 7. 两道评测门必须分开
|
|||
|
|
assert.match(panel, /隔离评测/, "必须有草稿的隔离评测入口(发布门禁之一)");
|
|||
|
|
assert.match(panel, /旧\/新对照/, "必须有候选的旧/新对照入口(另一道门)");
|
|||
|
|
// 草稿评测必须精确到 `组件id@版本`:只传组件 id 会评测到"当前最新草稿",
|
|||
|
|
// 于是出现"评测的是 A、发布的是 B"。
|
|||
|
|
assert.match(panel, /proposed_revision_ref\.id \+ "@" \+ item\.proposed_revision_ref\.version/,
|
|||
|
|
"草稿隔离评测必须精确定位到候选声明的版本");
|
|||
|
|
assert.match(panel, /泄漏/, "泄漏样本必须按失败显示,不得渲染成评测完成");
|
|||
|
|
|
|||
|
|
// ---------------------------------------------------------------- 8. AC-56:会议结果录入
|
|||
|
|
assert.match(trajectory, /review-outcomes/, "轨迹面板应消费会议结果端点");
|
|||
|
|
assert.match(trajectory, /"POST"/, "轨迹面板必须能追加会议结果");
|
|||
|
|
assert.match(trajectory, /raw_code/, "录入必须围绕原始结果码,不得自行推断通过或否决");
|
|||
|
|
assert.match(trajectory, /supersedes_id/, "更正只能追加并指向旧记录");
|
|||
|
|
assert.match(trajectory, /不计入分母/, "未匹配的结果必须单列且不计入分母");
|
|||
|
|
|
|||
|
|
// ---------------------------------------------------------------- 9. 后端真的接上了(交叉断言)
|
|||
|
|
assert.match(prescreenApi, /publisher=_knowledge_publisher\(/, "端点必须注入真实 publisher");
|
|||
|
|
assert.match(prescreenApi, /knowledge_cards\.publish\(db, draft\["id"\], actor, preview\)/,
|
|||
|
|
"publisher 必须调用真实知识发布入口");
|
|||
|
|
assert.ok(!/released_ref=payload\.released_ref/.test(prescreenApi),
|
|||
|
|
"端点不得再把客户端提交的 released_ref 透传给服务层");
|
|||
|
|
assert.match(prescreenApi, /不接受客户端提交的 released_ref/, "必须显式拒绝客户端自报版本号");
|
|||
|
|
assert.match(read("backend/services/improvements.py"), /released_ref = publisher\(body\)/,
|
|||
|
|
"服务层发布必须委托注入的 publisher");
|
|||
|
|
|
|||
|
|
// ---------------------------------------------------------------- 10. 不新增样式表 / 既有禁令
|
|||
|
|
assert.ok(!/^import .*\.css";/m.test(panel), "改进面板应复用 workspace.css,不得新增样式表");
|
|||
|
|
for (const [name, source] of [["improvement", panel], ["trajectory", trajectory]]) {
|
|||
|
|
assert.ok(!/0\.(42|58|51|64)\b/.test(source), `${name} 不得硬编码演示概率值`);
|
|||
|
|
assert.ok(!/"(00|10|01|11)"\s*:/.test(source), `${name} 不得复制演示情景表`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log("improvement-panel-ui: PASS");
|