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