docs/current/07-质量与裁定/20261007_jsonmbt真相源迁移.md
GitHub ↗
当前有效

.json.mbt 真相源迁移(jsonmbt 批②,2026-10-07)

3063 字·约 8 分钟 阅读 2026-10-10 17:07

形态

13 张 rules.json 翻转为 .json.mbt 唯一真相源,.json 由 jsonmbt build --pretty 在 CI 中再生,供下游生成器消费——消费方零改动:

rules.json.mbt(真相源,入仓)──jsonmbt build --pretty──▶ rules.json(CI 再生产物,不入仓)

--pretty 是硬要求:原 .json 为 Go json.Encoder SetIndent 形态(缩进 2 + 尾换行), 紧凑输出会让按字节锚定的对账漂移。

迁移清单(13 张 = 批② 11 张 + 手写面第二批两落 2 张)

文件 消费方闸门 CI job
scripts/moonbit/host_route_rules.json.mbt host_route_coverage core
scripts/moonbit/libc_boot_diff/rules.json.mbt libc_boot_diff core
scripts/moonbit/libc_single_source/rules.json.mbt libc_single_source -check core
scripts/moonbit/mbti_sync/rules.json.mbt mbti_sync 本地闸(CI 仍再生)
scripts/moonbit/pkg_deps/rules.json.mbt pkg_deps -check core
scripts/host_contract_map/rules.json.mbt host_contract_map --strict core
scripts/gen_protocol_ts/rules.json.mbt gen_protocol_ts -check core
scripts/perf_baseline/rules.json.mbt perf_baseline 本地闸(CI 仍再生)
scripts/perf_budget/rules.json.mbt perf_budget -check hygiene(job 内自建再生步,2026-10-09)
scripts/protocol_frames/rules.json.mbt protocol_frames core
scripts/skill_path_check/rules.json.mbt skill_path_check core
scripts/demo_ui_lint/rules.json.mbt demo_ui_lint(三路文本对账) core(2026-10-09 两落批)
scripts/teaching_annotation_diff/rules.json.mbt teaching_annotation_diff(族级 golden 对拍) core(2026-10-09 两落批)

本地开发须知(重要)

.json 是再生产物、不入仓,且被 .gitignore 遮蔽。干净检出后跑任何消费上述规则的 闸门之前,必须先再生,否则报 FATAL: 读规则失败(2026-10-07 实测:mbti_sync / perf_baseline 均如此):

# 方式一:逐张(jsonmbt exe 路径按本地实际位置)
"<jsonmbt.exe>" build --pretty scripts/moonbit/pkg_deps/rules.json.mbt

# 方式二:全量(13 张,一次跑完——按 `rules` 取面与 CI 同款,
# 刻意不含 5 张 golden_digest.json.mbt:其 .json 是入仓基线非再生面)
for m in $(git ls-files 'scripts/**/rules.json.mbt' 'scripts/**/host_route_rules.json.mbt'); do
  "<jsonmbt.exe>" build --pretty "$m"
done

jsonmbt exe 的获取见 jsonmbt 仓 README「在 Vitro CI 中的使用(实验性)」一节。

改数据时的纪律

改 .json.mbt(真相源),不要手改 .json——后者是再生产物,下次 CI 再生即被覆盖, 且手改不会触发任何门禁(不在索引内,闸门不读它的历史)。

13 张全部是源头输入(const rulesPath / os.ReadFile 读取,全仓无任何 WriteFile/Create 指向它们),不是某个生成器的产物——所以直接编辑 .json.mbt 是正确的。改动后本地复跑对应闸门确认。

改动 .json.mbt 后的本地三步(顺序别换):

moon fmt scripts      # ① 归一形态(必须限定 scripts 成员,否则误伤 moonbit)
cd scripts && moon check   # ② 静态检查(scripts 自立 workspace,2026-10-08 合流后形态)
"<jsonmbt.exe>" build --pretty <改动的>.json.mbt   # ③ 再生 .json 供下游闸门消费

若将来某张 .json 变成「某生成器的输出」,则应改那个生成器而非本文件—— 判定方法:grep -rn "<全路径>" --include="*.go" scripts/,只有读取、没有写入才是源头。

验证证据(2026-10-07 一手实测)

验证项 结果
import + check + build 11/11 rc=0
再生 vs 原 JSON 7 BYTE-EQUAL + 4 仅缩进风格差(值严格比对全 EQ,零浮点无争议面;第十一轮审阅 P2-1 勘误:此「4 张格式差」即「非字节全兼容」——正式口径为语义等价(消费方 JSON 解析),未来按字节锚定的消费方勿以本表推逐字节)
下游闸门输出逐字节对照 11/11 IDENTICAL(连 protocol_frames 12 处既有 DRIFT 的内容与位置都相同)
确定性锚(重生成 vs 现产物) 11/11 逐字节一致
红牙① 语法坏 J1001 红
红牙② levels.parser 4→0 pkg_deps -check 越层 5 条红
红牙③ vm_whitelist 删 exit host_route_coverage 1 条路由无实现红
CI 干净检出模拟 8+1 张再生 rc=0,core 7 闸 + hygiene 1 闸全绿

moon 静态门禁(批 C 试点,2026-10-07)

.json.mbt 相对 .json 的核心增量不是"更好读",而是MoonBit 编译器会替你看数据: 类型错、字段名错、字段缺失在编译期红,而写坏的 .json 照样能通过下游 Go 闸门。 真相源选 .json.mbt 的 buying reason 就在这一步——不接 moon,等于只换载体不换保护。

接法:scripts/ 自立 workspace(真相源原地不动)

scripts/moon.work                           # members = ["."](2026-10-08 合流后形态)
scripts/moon.mod                            # name = "vitro/scripts"(块式,非 moon.mod.json)
scripts/moon.pkg                            # 空块(1 字节换行)
scripts/<每个含 .json.mbt 的目录>/moon.pkg  # 13 个,逐目录(批② 11 + 两落批 2)

CI 与本地一律 cd scripts && moon check(实测 ran 24 tasks)。

勘正(2026-10-08 合流批):批②原先是仓根 moon.work members = ["./scripts", "./moonbit"] ——一条命令覆盖两 workspace。合流时改为 scripts 自立:实测根级 moon.work 会让 moonbit 产物整体搬到仓根、包路径加 vitro/engine/ 前缀,壳与防线的产物消费面全断,故 moonbit 保持 原生单模块、scripts 自带 moon.work members=["."]。

为什么逐目录 moon.pkg(硬要求,workspace 不改变这点):moon 只把自带 moon.pkg 的目录当包并编译其中的 .json.mbt;只在 scripts/ 放一个 moon.pkg 时 moon check 报 ran 1 task(文件根本没被读),逐目录加齐后才真正开始检查。

⚠️ moon fmt 必须限定成员,否则误伤 moonbit

在 workspace 根跑无参数 moon fmt 会重排所有成员的源文件——实测把 moonbit/codegen/addr.mbt 改了 13 行(match 分支缩进重排),而本批并未触碰该文件 (已 git checkout 回滚)。正确用法:

moon fmt scripts      # ✅ 只格式化 scripts,不碰 moonbit
moon fmt              # ❌ 全 workspace,误伤 moonbit

moon 的行为基线(本仓实测,勿再踩)

命令 是否检查孤立包中的 .json.mbt
moon build ❌ 注入类型错仍 rc=0
moon check(根级,workspace 覆盖) ✅ 逐张注入验证 11/11
moon check scripts(成员名当路径传) ❌ "no work to do"——成员名不被当作路径

三个前提(11 张已全部满足)

  1. 目录带 moon.pkg —— 否则 moon 完全看不见该文件
  2. struct 必须 pub —— priv 触发逐字段 unused_field 噪音,且 moon info 不透明
  3. 经 moon fmt scripts 归一 —— fmt 会补 ///| 文档标记,并按行宽决定折行;实测 pkg_deps 的 levels Map(26 项)被展开为逐行。归一后 build 产物仍与原 .json 等价(7 张字节相等 + 4 张仅缩进风格差、值相等),即 fmt 只改形态不改语义。 11/11 二次 fmt 幂等。

已知非致命面

  • module 是 moon 保留字 → Warning (reserved_keyword),rc 仍为 0。该字段名来自 rules.json 的数据键,不可改(改了就破坏 build 产物的键名)。
  • scripts/moon.work 会在 scripts/ 下生成 _build/(已由 scripts/**/_build/ 遮蔽)。

覆盖度证据(逐张注入,2026-10-07)

11 张全部跨类型注入(String 字段声明改 Int)→ 11/11 rc=127 [4014],恢复后 moon check rc=0。

⚠️ 注入必须跨类型:同类型改数值(如 Map[String, Int] 的值 4→0)moon 不红是 正确的——moon 抓类型/字段,不抓业务语义;业务语义由下游 Go 闸门(如 pkg_deps -check 的越层检测)负责,两者互补不可互相替代。

另一处假阴性记录:首次统计"8/11 受保护",实为注入 fallback 在文件尾追加 pub let——两个 pub let 在 MoonBit 语法上合法(moon 不知 D-7 的单文档基数), 故不红。哨兵不仅要"破坏落地",还要破坏在正确的那一层。

红牙(ci.yml 原文命令执行,非手抄)

注入 门禁
struct 声明与构造器字段不符 rc≠0 [4044]
String 字段声明改成 Int rc≠0 [4014]
恢复 rc=0,下游闸门与 jsonmbt build 均 rc=0

CI 接线与复绿(2026-10-08)

ci.yml 的 Build jsonmbt + Regenerate truth-source JSON 两步是 .json.mbt 的唯一供食线, 合流批后两处同时红(run 37733072522,HEAD 7c9c7e6d):

红点 根因 修法
Build jsonmbt rc=127 干净 runner 的 registry index 未初始化 ⇒ moonbitlang/parser·lexer·x 三个直接依赖全报 "module was not found in the registry",stderr 另给三行 you may need to run moon update。Vitro 侧其余 moon 步骤踩不到:scripts/moon.mod 零依赖、moonbit 侧依赖 moonbitlang/core 随工具链安装 bundle 该步骤内补 moon update(= Update the package registry index)
perf_budget -check FATAL exit 2 再生清单手抄 10 张、漏 perf_budget;其 rules.json 因 .gitignore 不入仓 ⇒ 漏再生即缺文件 清单改为按索引自枚举(git ls-files 'scripts/**/rules.json.mbt' 'scripts/**/host_route_rules.json.mbt')+ 计数断言,增删真相源而未同步此处即红

第二轮红(run 37883006515,HEAD fa9cd88d,2026-10-09 修)——两落批 (4913eece)加 2 张真相源但接线声称与实际不符(提交正文写了「CI 再生清单 +2 / .gitignore 遮蔽」而两文件实际未改):

红点 根因 修法
Core Regenerate FATAL 期望 11 实得 13 两落批的 2 张 rules.json.mbt 入仓命中自枚举 glob,但计数断言未同步——断言牙真实咬合(本红即实证) 断言 11→13;入仓 .json 两张删除(git rm)+ .gitignore 补遮蔽——形态对齐批② 11 张(单源)
Hygiene perf_budget -check FATAL 缺 rules.json(自 10f752ff 起连续红) job 间不共享工作区:core 的 Regenerate 产物带不进 hygiene job;perf_budget 是 hygiene job 唯一 rules 消费方,10f752ff 只修了 core 侧清单 hygiene job 内自建 Build jsonmbt(同款 clone + moon update + clang)+ 单张再生步,置于 perf_budget -check 前

本地红→绿证据(2026-10-09):13 张全量再生 rc=0;skill_path_check(missing=0)、 demo_ui_lint + -selftest(class=10/id=150/var=500)、teaching_annotation_diff 双臂 82/82 OK、perf_budget -check PASS——删 .json 后全靠再生供食,闸门输出与删除前一致 (两落批回验 BYTE-EQUAL 在案)。

本地红→绿证据(2026-10-08;用隔离 MOON_HOME 空 registry 模拟干净 runner):

  • 直接 build → 与 CI 逐字同错(三 Warning + 三条 not found)rc=127;moon update 后 build rc=0, 产物 _build/native/release/build/cmd/jsonmbt/jsonmbt.exe(4,003,840 B,路径与 ci.yml 的 JSONMBT_EXE 一致)
  • 干净检出(git clone --depth 1 file:///… 仅入仓文件)perf_budget -check → FATAL: 读规则失败: open …/scripts/perf_budget/rules.json,按新清单再生后 → perf_budget: PASS
  • 断言变异(pathspec 只取 rules.json.mbt ⇒ 10 张)→ 退出码 2 并打印清单(断言有牙)
  • 其余消费方闸门再生后全绿:pkg_deps -check / libc_single_source -check / host_contract_map --strict / gen_protocol_ts -check / skill_path_check

关联

  • 试点(批①,小测试):diagnostics_data 六张,走克隆仓库的 jsonmbt-pilot 分支
  • 正式迁移(本批):本文件所述 11 张——已合流 master(7c9c7e6d);开发分支 jsonmbt-migrate 内容已被合流吸收,仅作存档
  • 手写面第二批两落(2026-10-09,refs #47):teaching_annotation_diff / demo_ui_lint 两张入列(清单 11→13,接线收口见上第二轮红表);single_source 经 --fill 全量键集普查裁定为开放注记形态、维持 JSON 不迁(jsonmbt#14)
  • 工具侧:jsonmbt 仓 docs/PLAN.md §3 Vitro 协同 / §5 里程碑 / §9 试点实录