包清单与目录参考
每个单元以 pack.json 为清单。清单解析只做形状与取值范围的机械校验;契约兼容、落点、policy、签名与分类等语义判定交给 guard。实现见 src/core/kernel/manifest.ts,读取与核验见 src/core/kernel/inspect.ts。
三种包形态
| 形态 | 典型用途 | 读取方式与约束 |
|---|---|---|
| 目录树 | 开发、本地调试、编辑器直接产出 | list() 返回文件路径、大小与可查的来源信息;read(path) 读取字节。目录输入枚举到第 100,001 个文件即停,单元包按 100,000 个文件截止 |
| ZIP | 分发、下载、云端传输 | 只接受 store 与 deflate;拒绝分卷、ZIP64、加密、路径穿越、绝对路径、重名项与越界内容;条目即使随后跳过,也先核对安全路径;通过随机读取接口访问 |
| builtin | 随构建产物发布的内置数据与资源 | 产物内嵌的文件表,优先级最低;内置包只能是数据包或资源包 |
三种形态走同一套读取契约,上层不感知形态差异。目录形态在 Tauri 下可逐文件取得所有者与修改时间;Tauri 的大文件读取在宿主侧分成不超过 64 MiB 的块,并拒绝读取期间的大小变化;ZIP 只记录外部归档文件自身的信息,不冒充内部条目的信息;网页端只提供可查的修改时间,不伪造所有者。
目录布局
my-pack/
├── pack.json
├── signature.json # 代码单元签名;数据单元不要求
├── data/
│ └── script/
│ ├── graphs/
│ │ └── example/
│ │ └── arrival.json
│ └── chapters/
│ └── example/
│ └── arrival.json
├── assets/
│ └── example/
│ ├── bg/
│ │ └── street.png
│ └── lang/
│ └── zh-CN.json
└── code/
└── main.js # 代码单元唯一入口,打包后的单文件 ESM包内只允许 pack.json、signature.json 与 data/、assets/、code/ 三个内容根。assets/sui/ 是安全关键界面的保留命名空间,任何单元包含其下文件都会被拒绝。
数据包的 data/ 映射
数据包的文件路径直接决定 registry 与条目 ID:
data/<域>/<registry 名>/<命名空间>/<局部名>.json
→ registry「<域>:<registry 名>」中的条目「<命名空间>:<局部名>」例如 data/script/graphs/example/arrival.json 写入 script:graphs,条目 ID 为 example:arrival;文件内容须为:
{
"value": { "ir": "1.0", "id": "example:arrival", "entry": "start", "nodes": { "start": { "body": [ { "op": "core:end" } ] } }, "edges": [] },
"strategy": { "/nodes/start": "replace" }, "policy": "public"
}| 字段 | 必填 | 说明 |
|---|---|---|
value | 是 | 条目正文;剧本图须通过 IR v1 校验 |
strategy | 否 | JSON Pointer → replace/merge/append,字段级覆盖策略 |
policy | 否 | 新建条目取 closed 或 public |
路径中的命名空间 core 与 sui 保留给框架,单元不得用其创建条目。同一 registry 的条目按装载顺序分层叠加,卸载时整层移除。
资源包的 assets/ 映射
资源包的文件路径就是 VPath:assets/<命名空间>/<类型>/<路径>。资源包提供表现资产,不声明玩法逻辑或事件绑定。地图表现属于资源包,地图逻辑属于数据包。
界面皮肤有专门的受校验入口:assets/<命名空间>/skin/ 下的变量、九宫格与 controls.json 可以调整已登记控件的外观、位置、尺寸与局部层级;它不是任意 CSS。交互控件的点击区域下限、焦点框与键盘顺序由外壳保留。字段、CSS 像素单位与拒绝规则见 Platform 皮肤参考。
界面默认实现与皮肤登记
两种声明都由数据包的 data/ 映射登记,不是 pack.json 的额外顶层字段:
| 文件示例 | registry 与正文 |
|---|---|
data/ui/screen-defaults/example/home.json | ui:screen-defaults;value 为 { "screen": "core:home", "impl": "example:home" } |
data/ui/skins/example/theme.json | ui:skins;value 为 { "name": "示例皮肤", "tokens": "assets/example/skin/tokens.json", "slices": "assets/example/skin/slices.json", "controls": "assets/example/skin/controls.json" } |
界面声明指定的实现须由代码单元另行登记。用户选择优先于数据包声明;声明按装载顺序取最后一条,指定实现未登记时回落内置实现。皮肤的 tokens、slices、controls 至少提供一项,JSON 与贴图放在独立资源包;数据与资源不得混入同一单元。
内置默认皮肤由局部名为 default 的 ui:skins 条目登记,首次启动尚无用户选择时套用。示例实现可对照内置数据包的 data/ui/skins/framework/default.json 与 builtin/framework.skins/,但不要把内置示例名称当作第三方包的必需字段。
公共字段
| 字段 | 类型 | 必填 | 约束 |
|---|---|---|---|
format | number | 是 | 当前只能为 1;其他数字报 LOADER_VERSION_UNSATISFIED |
id | string | 是 | 稳定 ID,跨版本不变;[a-z0-9][a-z0-9.-]{2,127} |
version | string | 是 | 合法 semver,如 1.2.0、1.2.0-beta.1 |
name | string | 是 | 显示名,不能为空 |
publisher | string | 是 | 发布者名,不能为空;名称规范化与同形检测由 guard 执行 |
kind | string | 是 | datapack/resourcepack/plugin/mod/bundle |
name 与 publisher 是显示信息;任何安全判定都不依赖人类可读名称,身份以公钥指纹为准。
单元字段
| 字段 | 类型 | 适用 | 说明 |
|---|---|---|---|
files | object | 全部单元 | 相对路径 → sha256:<64 位小写十六进制>;覆盖除 pack.json 与 signature.json 外的全部文件,路径必须位于 data/、assets/、code/ 下 |
ir | string | 数据包、资源包 | 必填;声明兼容范围,如 ^1。图内 ir 是文档实际版本,两者含义不同 |
entry | string | 代码单元 | 必填;必须是 code/ 下的相对路径 |
targets | string[] | 全部单元 | 单元声明兼容的契约范围;每一项都必须与 RPM 中的根插件契约兼容 |
caps | array | 全部单元 | { name, optional? };能力名形如 caps:...;必需能力缺失即拒绝,可选能力缺失产生降级提示 |
budget | object | 代码单元必填 | 可含 archiveBytes、fileCount、fileBytes、ratio,值须为正数;不得大于默认上限 |
registers | array | 代码单元 | { slot, name, formats? };formats 只允许出现在 translate:codec@1 的登记项,且须为非空、去重的格式名数组;落点为 script:presentation@1 时,实现名即指令名 |
writes | object | 代码单元 | { hooks, intercepts, isolateProvides },缺省均为空数组 |
persistence | string | 代码单元必填 | none/closed/public;缺失由 guard 以 GUARD_PERSISTENCE_UNDECLARED 拒绝 |
flagNamespaces | string[] | 可选 | 单元持有的旗标命名空间,不得重复;core 与 sui 保留;同一命名空间只允许一个已装载单元声明 |
writes 的三类写边:
| 字段 | 形状 | 含义 |
|---|---|---|
hooks | [{ slot, phase }] | 在机制插槽上挂载;phase 取 before/after/replace |
intercepts | string[] | 改写目标服务的 resolved config |
isolateProvides | string[] | 在 isolate 域内提供服务的替代实现 |
只要存在至少一条写边,代码单元就是 Mod;没有写边是 Plugin。清单声明的 kind 必须与内容计算出的分类一致,否则报 GUARD_CLASSIFICATION_MISMATCH。
targets 对 Mod 另有要求:每一条 hooks 或 intercepts/isolateProvides 所作用目标所属的大插件,都必须在 targets 中出现对应的契约范围。
代码入口规则
代码单元的入口是打包后的单文件 ESM:
code/下只能有清单entry指向的这一个文件;多出文件或入口不在文件表内,报LOADER_MANIFEST_INVALID。- 包内模块之间的相对
import不提供;两端宿主都只暂存入口,作者须自行打包。 - 入口模块须导出
apply函数,由装载器以裁剪过的ctx调用。 - 单元不得在公共命名空间新增服务;对外提供的东西只经实现位或 registry。
registers 的落点必须是 RPM 声明过的实现位(RPM 的 kind=registry,如 script:presentation@1)。实现名在同一实现位内唯一;重名登记报 GUARD_SLOT_NAME_TAKEN。落点为 translate:codec@1 的插件判为隔离运行,入口只在专用 Worker 中执行,主 realm 不执行其任何代码;该登记项还须声明 formats。
分发包
分发包的根 pack.json 使用 kind: "bundle",并列出 units:
{
"format": 1,
"id": "example.bundle",
"version": "1.0.0",
"name": "示例分发包",
"publisher": "Example",
"kind": "bundle",
"units": ["story", "assets"]
}| 规则 | 说明 |
|---|---|
| 根目录内容 | 只能有 pack.json 与 units 列出的子单元目录 |
| 子单元 | 每个子目录有自己的 pack.json,分别判定、分别给出结果 |
| 嵌套 | 分发包不得嵌套分发包 |
| 重复 | units 不得为空、不得重复,路径须为安全的相对路径 |
| 混合 | 数据与资源混在同一单元里一律拒绝,须拆成两个子单元 |
分发包只是装载单位,不会把子单元合并成一个判定结果;任一子单元被拒不影响其他子单元继续判定。
归档 hash
归档 hash 用于未签名单元的批准与内置包登记,计算范围是单元全部文件:
- 对每个文件计算 SHA-256,得到「相对路径 →
sha256:<十六进制>」表;包含pack.json与signature.json。 - 对该表做 JCS 规范化。
- 对规范化后的 UTF-8 字节计算 SHA-256。
文件修改时间、所有者与重新打包方式都不参与该 hash;目录树、ZIP、内嵌三形态对同一内容得到相同结果。内容不变而重新打包,不会触发未签名单元的重新批准。
签名文件
signature.json 是独立文件,不是 pack.json 的字段:
{
"alg": "ed25519",
"publicKey": "<Base64 编码的公钥字节>",
"signature": "<Base64 编码的签名>"
}| 规则 | 说明 |
|---|---|
| 字段 | 只允许这三个字段;多字段或少字段都按签名无效处理 |
| 覆盖对象 | 对 pack.json 的 JCS 规范化字节签名;pack.json 内的 files 表再覆盖各内容文件 |
| 状态 | 签名存在但核验失败报 GUARD_SIGNATURE_INVALID,不得当成「未签名」 |
| 适用 | release 构建下代码单元必须签名;数据包与资源包不要求签名 |
| 未签名单元 | 首次装载须用户批准;归档 hash 变化须重新批准,并展示逐文件变化 |
签名核验失败、未签名、签名有效但发布者不在清单内是三种不同状态。判定与用户裁决流程见信任与签名。
校验与装载边界
| 检查 | 失败码 |
|---|---|
| JSON 无法解析、缺字段、路径非法、哈希格式不对、代码入口多文件 | LOADER_MANIFEST_INVALID |
format 不是受支持的版本 | LOADER_VERSION_UNSATISFIED |
| 签名存在但核验失败、核验后内容变化 | GUARD_SIGNATURE_INVALID(未签名单元为 LOADER_MANIFEST_INVALID) |
| 超出包预算或 IR 预算 | GUARD_BUDGET_EXCEEDED |
| 声明为数据单元却含代码、含写边或能力 | GUARD_CLASSIFICATION_MISMATCH |
| 写入未声明的 registry 或实现位 | GUARD_SLOT_UNDECLARED |
预算默认上限为:单个归档 1 GiB、文件数 100,000、目录数 100,000、路径最多 256 段、单文件 256 MiB、解压比 100:1。单元可以声明更小的值,不能超出;翻译归档读取另对单条目的压缩数据设 256 MiB 上限,并按读入量与解出量取大者计入输入预算。
产物不自动装载。 翻译或编辑器写出的目录只是产物;用户须逐个选择装载,随后仍走完整的清单解析、签名核验、准入判定与装载后核验。写出产物不授予信任,也不改变已有单元。
相关页面:分发包与包结构、剧本 IR v1、原因码、装载管线与 guard、翻译层。