t56-mutations.mjs 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. /**
  2. * 票 56 变异闸(第二/三轮):把本票要防的错法逐一注入,确认 t56 真的一致红;
  3. * 每条变异后按字节还原并校验哈希。判据只看退出码(不管道捕获子进程输出,票 37 的沙箱教训)。
  4. *
  5. * node .scratch/cockpit-real-data-wayfinding/verify/t56-mutations.mjs
  6. */
  7. import { spawnSync } from 'node:child_process'
  8. import { readFileSync, writeFileSync } from 'node:fs'
  9. import { createHash } from 'node:crypto'
  10. import { fileURLToPath } from 'node:url'
  11. const ROOT = fileURLToPath(new URL('../../../', import.meta.url))
  12. const GATE = '.scratch/cockpit-real-data-wayfinding/verify/t56-panel-summary-disclosure.mjs'
  13. const RENDERER = 'src/modules/cockpit/components/PanelBlocks.vue'
  14. const OVERVIEW = 'src/modules/cockpit/domain/overviewPanels.js'
  15. const MUTATIONS = [
  16. {
  17. id: 'M1',
  18. file: RENDERER,
  19. from: 'data-panel-summary-toggle',
  20. to: 'data-panel-summary-toggle @mouseenter="summaryOpen = true"',
  21. why: 'hover 自动展开被复活(第二轮维护者明令撤销)→ A3 必须红',
  22. },
  23. {
  24. id: 'M2',
  25. file: RENDERER,
  26. from: " { key: 'locate', field: 'locateNote', hook: 'data-locate-note' },\n",
  27. to: '',
  28. why: '摘要条目被漏掉一条(locateNote 从折叠清单消失=说明丢失)→ B 组必须红',
  29. },
  30. {
  31. id: 'M3',
  32. file: RENDERER,
  33. from: '<span v-show="summaryOpen"',
  34. to: '<span v-if="summaryOpen"',
  35. why: '折叠改成 v-if(DOM 里读不到文本,走查脚本与读屏都拿不到原话)→ E 组必须红',
  36. },
  37. {
  38. id: 'M4',
  39. file: RENDERER,
  40. from: 'const summaryOpen = ref(false)',
  41. to: 'const summaryOpen = ref(true)',
  42. why: '默认展开(顶部照样占高,验收第 1 条落空)→ A1 必须红',
  43. },
  44. {
  45. id: 'M5',
  46. file: RENDERER,
  47. from: ':aria-expanded="summaryOpen"',
  48. to: ':aria-expanded="true"',
  49. why: '可访问状态写死(读屏永远报"已展开",与屏上不一致)→ A4 必须红',
  50. },
  51. {
  52. id: 'M6',
  53. file: RENDERER,
  54. from: ' <!-- 还没取过数:说"正在取数"',
  55. to: ' <p v-if="view.gap" class="panel-blocks__gap" data-panel-gap>{{ view.gap }}</p>\n <!-- 还没取过数:说"正在取数"',
  56. why: 'gap 又在折叠入口之外裸渲染一份(同一句话画两遍、折叠形同虚设)→ E24 必须红',
  57. },
  58. {
  59. id: 'M7',
  60. file: RENDERER,
  61. from: " { key: 'note', field: 'note', hook: 'data-panel-note' },\n",
  62. to: '',
  63. why: '撤下第三轮补的 note 条目(预警设置两族口径说明又变隐形)→ A17 + C18 双向普查必须红',
  64. },
  65. {
  66. id: 'M8',
  67. file: OVERVIEW,
  68. from: ' gap: MONITORING_FAULT_GAP,',
  69. to: ' gapNotShown: MONITORING_FAULT_GAP,',
  70. why: '故障口径改挂到表外字段名(值仍在 domain、渲染层拿不到=说明丢失)→ C 组漏网判红 + D 组行为判红',
  71. },
  72. {
  73. id: 'M9',
  74. file: OVERVIEW,
  75. from: ' gap: MONITORING_FAULT_GAP,',
  76. to: " gap: MONITORING_FAULT_GAP,\n futureNote: '新加的口径说明',",
  77. why: 'domain 新增视图级说明字段而不进折叠表(这正是"靠人记得去查"会漏的场景)→ C 组双向普查必须红',
  78. },
  79. ]
  80. const hash = (path) => createHash('sha256').update(readFileSync(path)).digest('hex')
  81. /** 变异源文本按 CRLF 归一后再注入(票 37 的教训:锚点含 \n 而源文件是 CRLF 会假未命中)。 */
  82. const normalizeFor = (text, path) => (readFileSync(path, 'utf8').includes('\r\n') ? text.replace(/\n/g, '\r\n') : text)
  83. let failures = 0
  84. for (const m of MUTATIONS) {
  85. const path = ROOT + m.file.replaceAll('/', process.platform === 'win32' ? '\\' : '/')
  86. const before = hash(path)
  87. const source = readFileSync(path, 'utf8')
  88. const needle = normalizeFor(m.from, path)
  89. if (!source.includes(needle)) {
  90. console.log(`BAD ${m.id}: 变异锚点未命中(源码已变,需更新脚本)`)
  91. failures += 1
  92. continue
  93. }
  94. writeFileSync(path, source.replace(needle, normalizeFor(m.to, path)))
  95. const caught = spawnSync(process.execPath, [GATE], { cwd: ROOT, stdio: 'ignore' }).status !== 0
  96. writeFileSync(path, source)
  97. const restored = hash(path) === before
  98. if (!caught) {
  99. console.log(`MISS ${m.id}: 注入后闸门仍全绿 → 守卫没有牙(${m.why})`)
  100. failures += 1
  101. } else if (!restored) {
  102. console.log(`BAD ${m.id}: 还原后哈希不一致,文件状态可疑,立刻人工核对`)
  103. failures += 1
  104. } else {
  105. console.log(`OK ${m.id} ${m.why} → 闸门转红;还原按字节一致`)
  106. }
  107. }
  108. console.log(`\nT56 MUTATIONS: ${MUTATIONS.length - failures}/${MUTATIONS.length} 全部被抓住`)
  109. process.exitCode = failures ? 1 : 0