形态
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 张已全部满足)
- 目录带
moon.pkg—— 否则 moon 完全看不见该文件 struct必须pub—— priv 触发逐字段unused_field噪音,且moon info不透明- 经
moon fmt scripts归一 —— fmt 会补///|文档标记,并按行宽决定折行;实测pkg_deps的levelsMap(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 试点实录