Files

100 lines
6.5 KiB
JavaScript
Raw Permalink Normal View History

/**
* 改进对照与发布 界面源码守卫(纠偏第四批 · 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");