// tests/audit-mockup-library-binding.test.ts
// -----------------------------------------------------------------------------
// `scripts/audit-mockup-library-binding.mjs`（npm 别名 `consumer-mockup-binding`）的
// **整脚本**回归面。
//
// 🔴 **为什么是这一条**（[[INFRA-F138]] 棘轮落地后量出来的顺序）：它在 `package.json`
// 的 `files[]` 白名单里（经 `scripts/audit-mockup-*.mjs` 那条 glob）⇒ **随 npm 包发到
// 每个 consumer**，并被 `docs/CONSUMER_AUDIT_SETUP.md` 逐字列为可直接调用的 CLI。
// ⇒ 它的假绿会随下一个版本分发到所有下游产品，其余闸的假绿只影响本仓。
//
// 此前它的回归面是 `I` 类 —— 唯一覆盖它的 `tests/mockup-handdrawn-component.test.ts`
// 只 import 了 `checkNonInstanceComponent` 这一个纯函数（5 条用例）。闸本体有 CLI entry
// guard，**import 时刻意不跑 CLI** ⇒ 那份测试结构上碰不到 argv 解析、缓存两种形态、
// M0/M1/M30 的聚合、WARN 通路、以及**四个进程出口**。摘掉任一出口的接线，它全绿。
//
// ⛔ **闸本体一行没改。** fixture 里的组件 / fileKey 全用 `fx` / `FXKEY` 假名（本仓
// 「探针名必须全仓无命中」纪律），且散文里提别的闸时**去掉 `.mjs` 后缀**（带后缀会让
// 量具把那条闸误报成「已覆盖」—— [[INFRA-F138]] 逐字记的高报事故）。
//
// ⚠️ **为什么有些用例不走共享 harness 的 `runGate` 而自己 `spawnSync`**：`runGate` 底下是
// `execFileSync`，它在**成功**分支不返回 stderr（本仓已付过一次代价，同族 memory
// `harness-config-can-fabricate-defects`）。而本闸的 WARN 通路与「静默不跑」这两档
// **恰好都是 exit 0 且证据在 stderr / 在「stderr 也是空的」** ⇒ 必须 `spawnSync`。
//
// 覆盖：绿档非空过（fixture 自己的 fileKey 回显 + 反向钉）· M0 三条通路 · M1 含祖先路径
//   与锚定边界 · M30 四条通路 · handdrawn WARN 永不改退出码 · 缓存两种形态
//   · 四个出口各取一次终态 · `--json` 面与 argv 顺序
//   · 🔴 **两条如实登记的假绿通路**（空分母 · pnpm 布局），⛔ 两条都**不是背书**
// -----------------------------------------------------------------------------
import { describe, it, expect, afterEach } from 'vitest'
import { spawnSync } from 'node:child_process'
import { lstatSync, mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import {
  createGateFixture,
  runGate,
  expectGateRed,
  expectGateGreen,
  cleanupGateFixtures,
} from './lib/gate-fixture-root'

const GATE = 'scripts/audit-mockup-library-binding.mjs'
/** fixture 专用 fileKey —— 真仓库那份是 `DtZcMkhNy6qh6jbQQnhreQ`，两者不可能同时对上。 */
const FX_KEY = 'FXKEYlibbind1'
/** 真仓库在用的 fileKey，只作**反向钉**用（断言 stdout 里不出现它）。 */
const REAL_KEY = 'DtZcMkhNy6qh6jbQQnhreQ'

const extraRoots: string[] = []
afterEach(() => {
  cleanupGateFixtures()
  for (const r of extraRoots.splice(0)) rmSync(r, { recursive: true, force: true })
})

// ---------------------------------------------------------------------------
// fixture 构造
// ---------------------------------------------------------------------------

type Node = Record<string, unknown>

/** 缓存的**嵌套**形态：`{ file: { components, document } }`（闸走 `cached.file`）。 */
function nested(document: Node, components: Record<string, unknown> = {}) {
  return JSON.stringify({ file: { components, document } }, null, 2)
}
/** 缓存的**扁平**形态：`{ components, document }`（闸走 `?? cached` 那一侧）。 */
function flat(document: Node, components: Record<string, unknown> = {}) {
  return JSON.stringify({ components, document }, null, 2)
}

/**
 * 🔴 **`realpathSync` 不是可省的整理，是本文件能存在的前提**（2026-08-26 实测）：
 * `createGateFixture` 走 `mkdtempSync(tmpdir())`，而 macOS 的 `tmpdir()` 是
 * `/var/folders/…` —— **`/var` 自己就是指向 `/private/var` 的 symlink**。
 * 于是闸的 entry guard（`resolve(process.argv[1])` vs 已 realpath 的 `import.meta.url`）
 * 在**普通 fixture 里就已经落空**：初版本文件 25 条用例 **22 条红**，连「绕开 symlink
 * 直连」的阳性对照都是 exit 0 + 零输出。
 * ⇒ 这条缺陷的触发面比「pnpm 布局」宽得多：**任何**路径里含一层 symlink 就中。
 * ⇒ 判据逻辑那批用例必须从**已 canonical** 的 root 跑，否则测的是「闸没跑」。
 */
function build(cacheJson: string, key = FX_KEY): string {
  const root = createGateFixture({
    gate: GATE,
    prefix: 'mockup-libbind-fx',
    // `scripts/lib` 走 harness 默认的**软链**（不是拷）—— 本闸 2026-08-26 起 import
    // `./lib/is-cli-entry.mjs`（[[INFRA-F140]] 抽出的共享 CLI entry guard）。
    // ⚠️ 软链在这里是安全的，理由是 harness 判据 1 逐字写的那条：`scripts/lib` 那批
    //    **不自己算 REPO_ROOT** —— `isCliEntry` 只读调用方传进来的 `import.meta.url`，
    //    所以 ESM 把它 realpath 回真仓库也不影响判据。
    // 🔴 此处曾写 `linkDirs: []` 并注释「本闸只 import node: 内置模块」——
    //    抽共享 guard 当天那句话就失效了，本文件 25 条当场全红。留作记录：
    //    **给一批脚本引入共享 lib，会让它们所有的 fixture-root 测试同时需要那份 lib。**
    files: { [`figma-data/mockup/${key}.json`]: cacheJson },
  })
  return realpathSync(root)
}

/** 一棵干净的树：唯一的 INSTANCE 指向 remote=true 的库组件。 */
const CLEAN_DOC: Node = {
  id: '0:1',
  name: 'fx-page',
  type: 'CANVAS',
  children: [
    { id: '1:1', name: 'fx-submit', type: 'INSTANCE', componentId: 'cRemote' },
    { id: '1:2', name: 'fx-wrapper', type: 'FRAME', children: [] },
  ],
}
const CLEAN_COMPONENTS = { cRemote: { name: 'fx-lib/Submit', remote: true } }

/** 单个本地 INSTANCE = M0 违例。`name` / `componentName` 可换以触发 M1 / M30。 */
function localInstanceDoc(name: string, componentName = 'fx-local/Thing'): {
  doc: Node
  components: Record<string, unknown>
} {
  return {
    doc: {
      id: '0:1',
      name: 'fx-page',
      type: 'CANVAS',
      children: [{ id: '1:1', name, type: 'INSTANCE', componentId: 'cLocal' }],
    },
    components: { cLocal: { name: componentName, remote: false } },
  }
}

// ---------------------------------------------------------------------------
// 自己的 spawn（理由见文件头：execFileSync 成功分支丢 stderr）
// ---------------------------------------------------------------------------
function spawnAt(cwd: string, scriptPath: string, args: string[] = []) {
  const r = spawnSync(process.execPath, [scriptPath, ...args], { cwd, encoding: 'utf8' })
  return { status: r.status ?? -1, stdout: r.stdout ?? '', stderr: r.stderr ?? '' }
}

// ===========================================================================
describe('audit-mockup-library-binding — 绿档（非空过）', () => {
  it('全 remote INSTANCE ⇒ exit 0，且回显的是 fixture 自己的 fileKey', () => {
    const root = build(nested(CLEAN_DOC, CLEAN_COMPONENTS))
    const run = runGate(root, GATE, [FX_KEY])
    expectGateGreen(run, {
      contains: [
        // fileKey 是**只有真跑过 CLI 才可能出现的读数**（本仓「绿档必须钉一个只有真跑过
        // 才有的读数」纪律）—— 它由 argv 进来又被印回去，import 那条路上不存在。
        `✓ mockup library binding (${FX_KEY}): 0 findings`,
        'all INSTANCEs are remote library references',
      ],
    })
    // ⛔ 反向钉：跑错了树（退回真仓库的缓存）就会印真 fileKey
    expect(run.stdout).not.toContain(REAL_KEY)
  })

  it('缓存的**扁平**形态（无 `file` 包裹）同样被吃下 —— `cached.file ?? cached` 的另一侧', () => {
    const root = build(flat(CLEAN_DOC, CLEAN_COMPONENTS))
    expectGateGreen(runGate(root, GATE, [FX_KEY]), { contains: [`(${FX_KEY}): 0 findings`] })
  })
})

// ===========================================================================
describe('audit-mockup-library-binding — M0（库实例）三条通路', () => {
  it('INSTANCE 指向 file-local 组件 ⇒ 红并点名 M0 + 组件名 + remote=false', () => {
    const { doc, components } = localInstanceDoc('fx-submit')
    const root = build(nested(doc, components))
    expectGateRed(runGate(root, GATE, [FX_KEY]), {
      marker: '❌ mockup library binding',
      checks: ['[M0]', 'fx-local/Thing', 'remote=false', 'M0=1'],
    })
  })

  it('INSTANCE **缺** componentId ⇒ M0（与上一条是不同的 recordFinding 点）', () => {
    const doc: Node = {
      id: '0:1', name: 'fx-page', type: 'CANVAS',
      children: [{ id: '1:1', name: 'fx-orphan', type: 'INSTANCE' }],
    }
    expectGateRed(runGate(build(nested(doc)), GATE, [FX_KEY]), {
      checks: ['[M0]', 'INSTANCE missing componentId reference'],
    })
  })

  it('componentId 不在 file.components 映射里 ⇒ M0 broken reference（第三个点）', () => {
    const doc: Node = {
      id: '0:1', name: 'fx-page', type: 'CANVAS',
      children: [{ id: '1:1', name: 'fx-ghost', type: 'INSTANCE', componentId: 'cMissing' }],
    }
    expectGateRed(runGate(build(nested(doc, { cOther: { name: 'x', remote: true } })), GATE, [FX_KEY]), {
      checks: ['[M0]', 'cMissing', 'broken reference'],
    })
  })

  it('⛔ must-not-hit：remote=true 的 INSTANCE 不是违例', () => {
    const doc: Node = {
      id: '0:1', name: 'fx-page', type: 'CANVAS',
      children: [{ id: '1:1', name: 'fx-ok', type: 'INSTANCE', componentId: 'cR' }],
    }
    expectGateGreen(runGate(build(nested(doc, { cR: { name: 'fx-lib/Ok', remote: true } })), GATE, [FX_KEY]), {
      contains: ['0 findings'],
    })
  })
})

// ===========================================================================
describe('audit-mockup-library-binding — M1（Top bar 不许自画）', () => {
  it('节点自己叫 "Top bar" 且是本地 INSTANCE ⇒ M0 **且** M1', () => {
    const { doc, components } = localInstanceDoc('Top bar')
    expectGateRed(runGate(build(nested(doc, components)), GATE, [FX_KEY]), {
      checks: ['[M1]', 'Top bar / Header must be library instance', 'M0=1', 'M1=1'],
    })
  })

  it('**祖先**叫 "Header"（自己不叫）⇒ 仍报 M1（`ancestors.some` 那条接线）', () => {
    const doc: Node = {
      id: '0:1', name: 'fx-page', type: 'CANVAS',
      children: [{
        id: '1:1', name: 'Header', type: 'FRAME',
        children: [{ id: '2:1', name: 'fx-logo', type: 'INSTANCE', componentId: 'cLocal' }],
      }],
    }
    expectGateRed(runGate(build(nested(doc, { cLocal: { name: 'fx-local/Logo', remote: false } })), GATE, [FX_KEY]), {
      checks: ['[M1]', 'M1=1'],
    })
  })

  it('⛔ must-not-hit 锚定边界：`Top bar 2` **不**触发 M1（正则两端锚定），只报 M0', () => {
    // `isTopBarNamed` 逐字是 `^(top[\s\-_]*bar|header)$` —— 后缀会让它落空。
    // 这条钉的是判据的**边界**，不是它的存在：写宽了（去掉 `$`）本用例当场红。
    const { doc, components } = localInstanceDoc('Top bar 2')
    const run = runGate(build(nested(doc, components)), GATE, [FX_KEY])
    expectGateRed(run, { checks: ['M0=1', 'M1=0'] })
    expect(`${run.stderr}\n${run.stdout}`).not.toContain('[M1]')
  })
})

// ===========================================================================
describe('audit-mockup-library-binding — M30（图标必须是库实例）四条通路', () => {
  it('非 INSTANCE 的 `icon/*`（自画 VECTOR）⇒ M30', () => {
    const doc: Node = {
      id: '0:1', name: 'fx-page', type: 'CANVAS',
      children: [{ id: '1:1', name: 'icon/fx-Search', type: 'VECTOR' }],
    }
    expectGateRed(runGate(build(nested(doc)), GATE, [FX_KEY]), {
      checks: ['[M30]', 'is type VECTOR (not INSTANCE)', 'M30=1'],
    })
  })

  it('本地 INSTANCE 且**节点名**是 `icon/*` ⇒ M0 + M30', () => {
    const { doc, components } = localInstanceDoc('icon/fx-Close')
    expectGateRed(runGate(build(nested(doc, components)), GATE, [FX_KEY]), {
      checks: ['[M30]', 'M0=1', 'M30=1'],
    })
  })

  it('本地 INSTANCE 但只有**组件名**是 `icon/*`（节点名不是）⇒ 仍 M30（`||` 的另一侧）', () => {
    // 判据是 `isIconNamed(node.name) || isIconNamed(componentMeta.name)`。
    // 只测节点名那一侧的测试对这一侧零敏感 —— 两侧各取一次。
    const { doc, components } = localInstanceDoc('fx-glyph', 'icon/fx-Arrow')
    expectGateRed(runGate(build(nested(doc, components)), GATE, [FX_KEY]), {
      checks: ['[M30]', 'icon/fx-Arrow', 'M30=1'],
    })
  })

  it('⛔ must-not-hit：`COMPONENT` / `COMPONENT_SET` 命名成 `icon/*` 不算 M30', () => {
    const doc: Node = {
      id: '0:1', name: 'fx-page', type: 'CANVAS',
      children: [
        { id: '1:1', name: 'icon/fx-A', type: 'COMPONENT' },
        { id: '1:2', name: 'icon/fx-B', type: 'COMPONENT_SET' },
      ],
    }
    expectGateGreen(runGate(build(nested(doc)), GATE, [FX_KEY]), { contains: ['0 findings'] })
  })
})

// ===========================================================================
describe('audit-mockup-library-binding — handdrawn WARN 通路（🔴 永不改退出码）', () => {
  it('FRAME 名字撞 canonical 组件名 ⇒ 印 WARN 但 **exit 0**', () => {
    // ⛔ 这是一个独立的接线点：`handdrawnWarnings` 若被接进退出码，consumer 侧会因为
    // 一条**启发式** WARN 被拦住。只测 M0/M1/M30 的测试对它零敏感。
    // ⚠️ 走自己的 spawnSync：WARN 在 stderr 而这一档 exit 0，共享 harness 的
    //    execFileSync 在成功分支不返回 stderr（文件头已记）。
    const doc: Node = {
      id: '0:1', name: 'fx-page', type: 'CANVAS',
      children: [{ id: '1:1', name: 'fx primary button', type: 'FRAME' }],
    }
    const root = build(nested(doc))
    const run = spawnAt(root, join(root, GATE), [FX_KEY])
    expect(run.status).toBe(0)
    expect(run.stdout).toContain('0 findings')
    expect(run.stderr).toContain('处疑似手搓组件')
    expect(run.stderr).toContain('matched "button"')
  })

  it('`[[mock]]` 标记 ⇒ 连 WARN 都不印（逃逸口本身也是判据的一部分）', () => {
    const doc: Node = {
      id: '0:1', name: 'fx-page', type: 'CANVAS',
      children: [{ id: '1:1', name: 'fx button [[mock]]', type: 'FRAME' }],
    }
    const root = build(nested(doc))
    const run = spawnAt(root, join(root, GATE), [FX_KEY])
    expect(run.status).toBe(0)
    // 🔴 **正向锚点不可省**：初版这条只有下面那句 `.not.toContain`，于是在「闸压根没跑」
    //    时**照样绿**（实测：初版 25 条里恰好是这类 3 条空过通过）。⇒ 任何 must-not-hit
    //    都要配一个「只有真跑过才有的读数」。
    expect(run.stdout).toContain(`(${FX_KEY}): 0 findings`)
    expect(run.stderr).not.toContain('处疑似手搓组件')
  })

  it('⛔ must-not-hit：只有 FRAME/GROUP/RECTANGLE 参与 WARN —— `TEXT` 名叫 button 不算', () => {
    const doc: Node = {
      id: '0:1', name: 'fx-page', type: 'CANVAS',
      children: [{ id: '1:1', name: 'button', type: 'TEXT' }],
    }
    const root = build(nested(doc))
    const run = spawnAt(root, join(root, GATE), [FX_KEY])
    expect(run.status).toBe(0)
    expect(run.stdout).toContain(`(${FX_KEY}): 0 findings`) // 正向锚点，理由同上一条
    expect(run.stderr).not.toContain('处疑似手搓组件')
  })
})

// ===========================================================================
describe('audit-mockup-library-binding — 四个进程出口各取一次终态', () => {
  it('出口 A：缺 fileKey ⇒ exit 2 + Usage', () => {
    const root = build(nested(CLEAN_DOC, CLEAN_COMPONENTS))
    const run = runGate(root, GATE)
    expect(run.status).toBe(2)
    expect(`${run.stderr}\n${run.stdout}`).toContain('Usage:')
  })

  it('出口 B：缓存不存在 ⇒ exit 2 + 点名路径与补救命令（fail-closed，⛔ 不是静默通过）', () => {
    const root = build(nested(CLEAN_DOC, CLEAN_COMPONENTS))
    const run = runGate(root, GATE, ['FXKEYnotcached'])
    expect(run.status).toBe(2)
    const out = `${run.stderr}\n${run.stdout}`
    expect(out).toContain('Mockup data not cached')
    expect(out).toContain('figma-data/mockup/FXKEYnotcached.json')
    // 路径必须落在 fixture 内 —— 这同时是「无 fallback 回真仓库」的凭据
    expect(out).toContain(root)
  })

  it('出口 C：有 finding ⇒ exit 1', () => {
    const { doc, components } = localInstanceDoc('fx-submit')
    expect(runGate(build(nested(doc, components)), GATE, [FX_KEY]).status).toBe(1)
  })

  it('出口 D：无 finding ⇒ exit 0', () => {
    expect(runGate(build(nested(CLEAN_DOC, CLEAN_COMPONENTS)), GATE, [FX_KEY]).status).toBe(0)
  })
})

// ===========================================================================
describe('audit-mockup-library-binding — `--json` 面与 argv 顺序', () => {
  it('`--json` 有 finding ⇒ 结构化载荷 + 退出码与人读面**一致**（1）', () => {
    const { doc, components } = localInstanceDoc('icon/fx-Close')
    const run = runGate(build(nested(doc, components)), GATE, [FX_KEY, '--json'])
    expect(run.status).toBe(1)
    const report = JSON.parse(run.stdout)
    expect(report.summary.fileKey).toBe(FX_KEY)
    expect(report.summary.totalFindings).toBe(2) // M0 + M30
    expect(report.summary.byRule).toEqual({ M0: 1, M30: 1 })
    expect(report.findings.map((f: { rule: string }) => f.rule).sort()).toEqual(['M0', 'M30'])
  })

  it('`--json` 无 finding ⇒ exit 0，且**不**印人读面那句 ✓（两个分支互斥）', () => {
    const run = runGate(build(nested(CLEAN_DOC, CLEAN_COMPONENTS)), GATE, [FX_KEY, '--json'])
    expect(run.status).toBe(0)
    expect(run.stdout).not.toContain('✓ mockup library binding')
    expect(JSON.parse(run.stdout).summary.totalFindings).toBe(0)
  })

  it('argv 顺序无关：`--json` 写在 fileKey **前面**照样解析出 fileKey', () => {
    // `args.find(a => !a.startsWith("--"))` —— 改成 `args[0]` 本用例当场红。
    const run = runGate(build(nested(CLEAN_DOC, CLEAN_COMPONENTS)), GATE, ['--json', FX_KEY])
    expect(run.status).toBe(0)
    expect(JSON.parse(run.stdout).summary.fileKey).toBe(FX_KEY)
  })
})

// ===========================================================================
// 🔴 两条曾经如实钉住「假绿现行行为」的 describe —— owner 2026-08-26 拍定整批修后
//    **双双翻面**。⛔ 那次红是这两条钉在履行职责，不是测试写错了。
// ===========================================================================
describe('空分母 fail-closed（[[INFRA-F140]] 修后；⛔ 这条曾经钉的是相反的行为）', () => {
  // 病根值得留着：`classifyLibraryBindingFindings` 一直算了 `scannedNodes`，而 CLI
  //   只解构 `{ findings, handdrawnWarnings }` 把它丢了 ⇒ `✓ 0 findings` 在「扫了
  //   5000 个节点全干净」与「一个节点都没扫」两种情形下**逐字相同**。比
  //   `audit-product-code` 的 `--dir` 那条更难发现，因为那条至少印了 `scanned 0 files`。

  it('缓存里没有 `document` ⇒ **exit 2** 并点名空分母（⛔ 不再是 ✓ 0 findings）', () => {
    const root = build(JSON.stringify({ file: { components: {} } }))
    // 🔑 三样一起钉：退出码 + 点名判据 + 补救指路。只钉退出码会被「碰巧崩在 2」骗过。
    expectGateRed(runGate(root, GATE, [FX_KEY]), {
      status: 2,
      marker: '空分母（scanned 0 nodes）',
      checks: [FX_KEY, '不可解读', 'pnpm sync:mockup'],
    })
  })

  it('`--allow-empty` 放行，但**必须把分母为 0 印出来**（逃逸口不许静默）', () => {
    // ⛔ 逃逸口的价值全在「它不静默」：放行后仍 exit 0，但绿档不再和真绿长得一样。
    // ⚠️ 走自己的 spawnSync：这一档 exit 0 而证据在 stderr（console.warn）。
    const root = build(JSON.stringify({ file: { components: {} } }))
    const run = spawnAt(root, join(root, GATE), [FX_KEY, '--allow-empty'])
    expect(run.status).toBe(0)
    expect(run.stderr).toContain('已由 --allow-empty 显式放行')
    expect(run.stderr).toContain('别把这次的 exit 0 读成')
  })

  it('🔑 绿档现在带分母 —— `scanned N nodes` 是 fixture 自己的数', () => {
    // ⛔ 这一半单独就能治「读不出来」：看到 `scanned 1 nodes` 的人立刻知道不对，
    //    而光看 `0 findings` 看不出。CLEAN_DOC = 1 个 CANVAS + 2 个 children = 3 节点。
    expectGateGreen(runGate(build(nested(CLEAN_DOC, CLEAN_COMPONENTS)), GATE, [FX_KEY]), {
      contains: ['0 findings in 3 scanned nodes'],
    })
  })
})

describe('🔴 pnpm 布局：经 symlink 调用**必须真的跑**（[[INFRA-F140]] 修后的回归钉）', () => {
  it('symlink 侧与直连侧行为一致；⛔ 这条曾经钉的是相反的（假绿）行为', () => {
    // 🔴 **本用例 2026-08-26 翻过面，是设计意图不是返工**：
    //    初版钉的是**假绿现行行为**（symlink 下 exit 0 + stdout/stderr 双空），并逐字标注
    //    「不是背书」+「谁改了它这里会当场红」。owner 当日拍定整批修 ⇒ 它当场红 ⇒ 按约翻面。
    //    ⇒ 那次红**就是这条钉在履行职责**，不是测试写错了。
    //
    // 根因（真源 = `scripts/lib/is-cli-entry.mjs` 头注释，⛔ 别在这里维护第二份）：
    //    `argv[1]` 不解析 symlink，而 `import.meta.url` 已 realpath ⇒ 手写守卫在路径含
    //    任一层 symlink 时恒假 ⇒ 整条 CLI 静默不跑。本闸原先用 `resolve(process.argv[1])`
    //    —— `path.resolve` 只规范化、**不解 symlink**，所以同样中招。
    //
    // 🔴 **这不是假想布局**：pnpm 的 `node_modules/@scope/pkg` 本身就是 symlink，
    //    `templates/consumer-product/scripts/link-tvu-ds.mjs` 的本地联调直接软链，
    //    连 macOS 的 `os.tmpdir()`（`/var` → `/private/var`）都带一层。
    const { doc, components } = localInstanceDoc('fx-submit')
    const root = build(nested(doc, components))

    // ---- 造一棵 pnpm 形状的假 consumer ----
    const consumer = mkdtempSync(join(tmpdir(), 'mockup-libbind-consumer-'))
    extraRoots.push(consumer)
    const scopeDir = join(consumer, 'node_modules', '@ux-team')
    mkdirSync(scopeDir, { recursive: true })
    const linkPath = join(scopeDir, 'tvu-design-system')
    symlinkSync(root, linkPath, 'dir')

    // fail-closed 自证：这一层必须真是 symlink 且 realpath ≠ 调用路径，
    // 否则本用例是空过（在某些文件系统上两者可能相等）。
    expect(lstatSync(linkPath).isSymbolicLink()).toBe(true)
    expect(realpathSync(linkPath)).not.toBe(linkPath)

    // ---- 照文档教的形态调用：consumer 仓库根 cwd + node_modules 相对路径 ----
    // ⚠️ 仍必须 spawnSync（不用共享 harness 的 runGate）：假绿态是 exit 0 且证据在
    //    「stderr 也是空的」，而 execFileSync 的成功分支会把 stderr 丢掉 ⇒ 一旦本条
    //    再回归成假绿，用 runGate 写的断言可能看不出来。
    const viaLink = spawnAt(consumer, `node_modules/@ux-team/tvu-design-system/${GATE}`, [FX_KEY])

    // 🔑 三样一起钉，缺一档都测不出来：
    //    ① 退出码不是 0（假绿态恰好是 0）
    expect(viaLink.status).toBe(1)
    //    ② 输出不是空的（「exit 0 且零输出」是本仓独立登记的可疑信号）
    expect(`${viaLink.stdout}${viaLink.stderr}`).not.toBe('')
    //    ③ 点名判据 + 回显 fixture 自己的 fileKey（只有真跑过才可能出现的读数）
    expect(viaLink.stderr).toContain('[M0]')
    expect(viaLink.stderr).toContain(`(${FX_KEY})`)

    // ---- 直连对照：同一棵 fixture、同一个 fileKey，绕开 symlink ----
    // 修好之后两侧必须**完全一致**；修坏了（守卫恒假）只有 symlink 那侧会退化。
    const direct = spawnAt(root, join(root, GATE), [FX_KEY])
    expect(direct.status).toBe(viaLink.status)
    expect(direct.stderr).toContain('[M0]')
    expect(direct.stderr).toContain('M0=1')
  })
})
