Skip to content

[finding] check-adr-0087-registration.mjs 的 CLI 派发在模块顶层执行 —— 任何 import 都会真的把门跑一遍,判红时还会 process.exit(1) 掐死调用方 #6566

Description

@hotlong

发现于 #6494 / PR #6556 实施期间(devx 车道执行座位)。按 Prime Directive #10 只记录不修,未认领不自评级别(定级是分诊座位的单一通道)。

事实

scripts/check-adr-0087-registration.mjs 的 CLI 派发写在模块顶层,没有 import.meta.url === process.argv[1] 这类入口守卫(同仓 scripts/objectui-changeset-digest.mjs 末尾就有一个)。文件尾部形如:

const argv = process.argv.slice(2);
if (argv.includes('--self-test')) { selfTest(); }
else if (argv.includes('--list')) { list(REPO_ROOT, 'HEAD'); }
else if (argv.includes('--audit-stock')) { auditStock(REPO_ROOT, ...); }
else { /* 真正的门:解析 base、assertInputs、scan、打印判决 */ }

四个分支没有一个是 no-op。因此 import 它 = 运行它(对导入方进程的 REPO_ROOTprocess.argv)。

实测(不是从代码形状推断的)

临时仓 + 从 origin/main 取出的门脚本副本,写一个只想借一个纯函数的消费方:

// probe.mjs
import { readDisposition } from './scripts/check-adr-0087-registration.mjs';
console.log('PROBE OWN OUTPUT: readDisposition ->', JSON.stringify(readDisposition('nothing here')));

第一次(门无可判) —— 门的判决先于消费方自己的那行打出来:

=== everything below this line came from a bare `import` of the gate module ===
✓ check-adr-0087-registration: this PR adds no declared-breaking changeset (0 non-breaking changeset(s) seen).
PROBE OWN OUTPUT: readDisposition -> {"ok":false,"reason":"no `adr-0087:` disposition marker"}
PROBE_EXIT=0

第二次(同一个 import,但仓里有一份声明破坏、没带标记的 changeset,默认 base 落在 HEAD 之前) —— 门判红并 process.exit(1)消费方自己的代码一行都没执行到

✗ check-adr-0087-registration: 1 problem(s).

  • .changeset/new-breaking.md
      declares a breaking change (major, BREAKING) but no `adr-0087:` disposition marker.
      …
PROBE_EXIT=1
--- did the importer's own line ever run? ---
NO — the gate called process.exit(1); the importer never reached its own code

即:导入方拿不到函数、拿不到控制权、还会被按门的判决决定退出码,而门判的是导入方的 REPO_ROOT,与导入方想问的问题无关。

这正是 PR #6556 绕开它的原因

#6494 要让 objectui-changeset-digest.mjs 生成的 changeset 携带一份「门仍会判红」的 ADR-0087 处置脚手架。想证明「门确实把这份占位认成 present-but-unanswered」,最直接的写法是 import 门的 parseChangeset / breakingDeclaration / readDisposition —— 一个判据、两个消费方、不会漂移,正是本仓一贯偏好的形状(objectui-range.mjs 与 digest 共用 classifyRange() 就是先例)。

这条堵死了这个写法。PR #6556 改为子进程 + 临时仓驱动门的二进制来钉一致性。附带一条也值得记:即便可以 import,也要权衡「给发布关键路径(bump-objectui.sh 调用链)加一个『门被改名/移动就崩』的耦合点」;两条理由叠加,子进程是对的,但第一条本不该存在

代价

一个不能被安全 import 的门,等于强迫每一个想与它保持一致的调用方自己起进程。 具体开销:

  • 每个消费方都要自建临时仓 + 复制门脚本 + 造够 assertInputs 的输入(两份 ledger 源、spec-changes.json、一份存量 breaking changeset),只为问一句「这段正文算不算带了处置标记」。PR fix(scripts): objectui digest 声明破坏性时写入门仍判红的 ADR-0087 处置脚手架 (#6494) #6556 为此写了约 40 行 fixture。
  • 这些 fixture 复制了门的输入契约:门以后改输入要求,邻居脚本的自测会跟着红。子进程把耦合从「模块路径」换成了「输入契约」,没有消灭它。
  • 反向作用同样存在:任何工具(未来的门、报表、审计脚本)想复用 extractIds / hasMigrationPrescription / readDisposition 这些明显是纯函数的导出,都会踩同一颗雷,而且踩到时的现象是「我的脚本莫名其妙打印了一段 ADR-0087 判决然后退出 1」,第一眼根本不像 import 的锅。

它们全部导出了(export function readDisposition 等),形状上就是给人复用的;只是现在没有一个可以被使用

相邻(非重复)

去重检索:adr-0087 importimport.meta.url top-level CLI dispatchcheck-adr-0087-registration 三次 search_issues 均无同形单。

发现会话:session_01BDmDsu2575gDxeMCxXhDE3(devx 车道执行座位)。严重度与路由留分诊座位判定。


Generated by Claude Code

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions