| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115 |
- /**
- * 票 56 变异闸(第二/三轮):把本票要防的错法逐一注入,确认 t56 真的一致红;
- * 每条变异后按字节还原并校验哈希。判据只看退出码(不管道捕获子进程输出,票 37 的沙箱教训)。
- *
- * node .scratch/cockpit-real-data-wayfinding/verify/t56-mutations.mjs
- */
- import { spawnSync } from 'node:child_process'
- import { readFileSync, writeFileSync } from 'node:fs'
- import { createHash } from 'node:crypto'
- import { fileURLToPath } from 'node:url'
- const ROOT = fileURLToPath(new URL('../../../', import.meta.url))
- const GATE = '.scratch/cockpit-real-data-wayfinding/verify/t56-panel-summary-disclosure.mjs'
- const RENDERER = 'src/modules/cockpit/components/PanelBlocks.vue'
- const OVERVIEW = 'src/modules/cockpit/domain/overviewPanels.js'
- const MUTATIONS = [
- {
- id: 'M1',
- file: RENDERER,
- from: 'data-panel-summary-toggle',
- to: 'data-panel-summary-toggle @mouseenter="summaryOpen = true"',
- why: 'hover 自动展开被复活(第二轮维护者明令撤销)→ A3 必须红',
- },
- {
- id: 'M2',
- file: RENDERER,
- from: " { key: 'locate', field: 'locateNote', hook: 'data-locate-note' },\n",
- to: '',
- why: '摘要条目被漏掉一条(locateNote 从折叠清单消失=说明丢失)→ B 组必须红',
- },
- {
- id: 'M3',
- file: RENDERER,
- from: '<span v-show="summaryOpen"',
- to: '<span v-if="summaryOpen"',
- why: '折叠改成 v-if(DOM 里读不到文本,走查脚本与读屏都拿不到原话)→ E 组必须红',
- },
- {
- id: 'M4',
- file: RENDERER,
- from: 'const summaryOpen = ref(false)',
- to: 'const summaryOpen = ref(true)',
- why: '默认展开(顶部照样占高,验收第 1 条落空)→ A1 必须红',
- },
- {
- id: 'M5',
- file: RENDERER,
- from: ':aria-expanded="summaryOpen"',
- to: ':aria-expanded="true"',
- why: '可访问状态写死(读屏永远报"已展开",与屏上不一致)→ A4 必须红',
- },
- {
- id: 'M6',
- file: RENDERER,
- from: ' <!-- 还没取过数:说"正在取数"',
- to: ' <p v-if="view.gap" class="panel-blocks__gap" data-panel-gap>{{ view.gap }}</p>\n <!-- 还没取过数:说"正在取数"',
- why: 'gap 又在折叠入口之外裸渲染一份(同一句话画两遍、折叠形同虚设)→ E24 必须红',
- },
- {
- id: 'M7',
- file: RENDERER,
- from: " { key: 'note', field: 'note', hook: 'data-panel-note' },\n",
- to: '',
- why: '撤下第三轮补的 note 条目(预警设置两族口径说明又变隐形)→ A17 + C18 双向普查必须红',
- },
- {
- id: 'M8',
- file: OVERVIEW,
- from: ' gap: MONITORING_FAULT_GAP,',
- to: ' gapNotShown: MONITORING_FAULT_GAP,',
- why: '故障口径改挂到表外字段名(值仍在 domain、渲染层拿不到=说明丢失)→ C 组漏网判红 + D 组行为判红',
- },
- {
- id: 'M9',
- file: OVERVIEW,
- from: ' gap: MONITORING_FAULT_GAP,',
- to: " gap: MONITORING_FAULT_GAP,\n futureNote: '新加的口径说明',",
- why: 'domain 新增视图级说明字段而不进折叠表(这正是"靠人记得去查"会漏的场景)→ C 组双向普查必须红',
- },
- ]
- const hash = (path) => createHash('sha256').update(readFileSync(path)).digest('hex')
- /** 变异源文本按 CRLF 归一后再注入(票 37 的教训:锚点含 \n 而源文件是 CRLF 会假未命中)。 */
- const normalizeFor = (text, path) => (readFileSync(path, 'utf8').includes('\r\n') ? text.replace(/\n/g, '\r\n') : text)
- let failures = 0
- for (const m of MUTATIONS) {
- const path = ROOT + m.file.replaceAll('/', process.platform === 'win32' ? '\\' : '/')
- const before = hash(path)
- const source = readFileSync(path, 'utf8')
- const needle = normalizeFor(m.from, path)
- if (!source.includes(needle)) {
- console.log(`BAD ${m.id}: 变异锚点未命中(源码已变,需更新脚本)`)
- failures += 1
- continue
- }
- writeFileSync(path, source.replace(needle, normalizeFor(m.to, path)))
- const caught = spawnSync(process.execPath, [GATE], { cwd: ROOT, stdio: 'ignore' }).status !== 0
- writeFileSync(path, source)
- const restored = hash(path) === before
- if (!caught) {
- console.log(`MISS ${m.id}: 注入后闸门仍全绿 → 守卫没有牙(${m.why})`)
- failures += 1
- } else if (!restored) {
- console.log(`BAD ${m.id}: 还原后哈希不一致,文件状态可疑,立刻人工核对`)
- failures += 1
- } else {
- console.log(`OK ${m.id} ${m.why} → 闸门转红;还原按字节一致`)
- }
- }
- console.log(`\nT56 MUTATIONS: ${MUTATIONS.length - failures}/${MUTATIONS.length} 全部被抓住`)
- process.exitCode = failures ? 1 : 0
|