t44-mutations.mjs 4.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. /**
  2. * 票 44 变异闸:把守卫要防的错法逐一注入,确认 t44 真的转红;每次变异后按字节还原并校验哈希。
  3. *
  4. * 判据只看退出码(不用管道捕获子进程输出——那是本环境的已知边界)。
  5. * 用法:node .scratch/cockpit-real-data-wayfinding/verify/t44-mutations.mjs
  6. * 先单独跑一次 t44 看绿,再跑本脚本看每条变异都被抓住。
  7. */
  8. import { spawnSync } from 'node:child_process'
  9. import { readFileSync, writeFileSync } from 'node:fs'
  10. import { createHash } from 'node:crypto'
  11. import { fileURLToPath } from 'node:url'
  12. const ROOT = fileURLToPath(new URL('../../../', import.meta.url))
  13. const GATE = '.scratch/cockpit-real-data-wayfinding/verify/t44-distribution-ranking.mjs'
  14. const MUTATIONS = [
  15. {
  16. id: 'M1',
  17. file: 'src/modules/cockpit/domain/panelBlocks.js',
  18. from: ' .sort((a, b) => b.count - a.count || a.index - b.index)',
  19. to: ' .sort((a, b) => a.index - b.index)',
  20. why: '排序改恒等(遗漏降序)→ RANK 组必须全红',
  21. },
  22. {
  23. id: 'M2',
  24. file: 'src/modules/cockpit/domain/abnormalCounts.js',
  25. from: ' }))',
  26. to: ' })).sort((a, b) => b.count - a.count)',
  27. why: '把固定语义序的异常桶强行降序(非目标第 2 条)→ FIXED 组必须红',
  28. },
  29. {
  30. id: 'M3',
  31. file: 'src/modules/cockpit/components/PanelBlocks.vue',
  32. from: ' gap: calc(4 * var(--u, 1px));\n}\n\n.panel-bar__label {',
  33. to: ' gap: var(--space-2, 8px);\n}\n\n.panel-bar__label {',
  34. why: '文字与统计条退回宽间距(收紧被撤销)→ B3 必须红',
  35. },
  36. {
  37. id: 'M4',
  38. file: 'src/modules/cockpit/components/PanelBlocks.vue',
  39. from: ' box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.85);\n',
  40. to: '',
  41. why: '删掉条段端帽(分割消失)→ B4b 必须红',
  42. },
  43. {
  44. id: 'M5',
  45. file: 'src/modules/cockpit/components/PanelBlocks.vue',
  46. from: ' box-shadow: inset 0 0 0 1px rgba(1, 171, 234, 0.12);\n',
  47. to: '',
  48. why: '删掉轨道发丝边(条段无容器边界)→ B4 必须红',
  49. },
  50. {
  51. id: 'M6',
  52. file: 'src/modules/cockpit/components/PanelBlocks.vue',
  53. from: ' <EllipsisText class="panel-bar__label" :text="row.label" />',
  54. to: ' <span class="panel-bar__label">{{ row.label }}</span>',
  55. why: '行标签退回裸 span(票 37 省略接缝被视觉改动带走)→ B7 必须红',
  56. },
  57. ]
  58. const hash = (path) => createHash('sha256').update(readFileSync(path)).digest('hex')
  59. /** 变异源文本按 CRLF 归一后再注入(票 37 的教训:PowerShell 写盘是 CRLF,锚点含 \n 会假未命中)。 */
  60. const normalizeFor = (text, path) => (readFileSync(path, 'utf8').includes('\r\n') ? text.replace(/\n/g, '\r\n') : text)
  61. let failures = 0
  62. for (const m of MUTATIONS) {
  63. const path = ROOT + m.file.replaceAll('/', process.platform === 'win32' ? '\\' : '/')
  64. const before = hash(path)
  65. const source = readFileSync(path, 'utf8')
  66. const needle = normalizeFor(m.from, path)
  67. if (!source.includes(needle)) {
  68. console.log(`BAD ${m.id}: 变异锚点未命中(源码已变,需更新脚本)`)
  69. failures += 1
  70. continue
  71. }
  72. writeFileSync(path, source.replace(needle, normalizeFor(m.to, path)))
  73. const status = spawnSync(process.execPath, [GATE], { cwd: ROOT, stdio: 'ignore' }).status
  74. writeFileSync(path, source)
  75. const restored = hash(path) === before
  76. const red = status !== 0
  77. console.log(`${red && restored ? 'OK ' : 'BAD '} ${m.id} ${m.why} → 闸门${red ? '转红' : '未转红'};还原${restored ? '按字节一致' : '**不一致**'}`)
  78. if (!red || !restored) failures += 1
  79. }
  80. console.log('')
  81. console.log(failures ? `T44 MUTATIONS: ${failures} FAILED` : `T44 MUTATIONS: ${MUTATIONS.length}/${MUTATIONS.length} 全部被抓住`)
  82. process.exit(failures ? 1 : 0)