Kernel(内核)
| 项 | 内容 |
|---|---|
| 契约版本 | kernel@1 |
| 当前状态 | 已完成 |
| 源码目录 | src/core/kernel/ |
| 页面定位 | 内核与装载管线参考:单元如何被读取、核验、准入、执行与卸载 |
Kernel 是不承载玩法功能的根插件:它决定「什么可以进入框架、以什么身份进入、能拿到什么」,但不解释剧本、不绘制画面、不播放音频。
职责
负责:
- 引导与生命周期:把 RPM、宿主后端、信任绑定与根入口组织成一次启动。
- 装载器:目录、ZIP、内置三种包形态的统一读取与准入。
- guard 准入与审计:四档扩展模型判定、写边核对、原因码记录。
- 签名核验与信任存储绑定:官方清单验签、序号防回滚、个人批准。
- 清单与版本求解:入口、依赖、预算、文件表与归档哈希。
- 能力注入:按单元清单与宿主可用性裁剪后注入。
- 包读取实现位:
kernel:pack-reader@1管理三种内置形态及第三方新形态。
不负责:
- 不提供面向单元的装载入口;
kernel:loader只读查询与核对,不暴露load。 - 不提供任何机制插槽;准入判定与装载管线不可被 Mod 改写。
- 不解析剧本 IR、不定义资源交付形态、不做文本排版或演出。
- 不读取整份个人信任库;装载器只取得待判单元相关快照、官方名单视图与裁决结果。
对外接口
服务
| 名称 | policy | 说明 |
|---|---|---|
kernel:loader | closed | units() 返回已装载单元信息;pending() 返回正在准入或等待裁决的稳定 ID;authorize(caller, request) 供插槽内部核对调用方归属与清单声明,不构成用户授权入口 |
caps:* | closed | 按单元注入的能力服务;只在该单元的隔离作用域提供已批准且可用的能力 |
guard:report | closed | entries() 返回判定报告;只在 Kernel 自身的隔离作用域可见 |
kernel:unit-files | closed | 只读访问已装载数据包与资源包:read(unitId, path) 每次复核 sha256,files(unitId) 列出内容文件路径;代码单元的文件与清单不经此暴露 |
units() 的每项包含 id、version、name、publisher、kind、signed,以及清单声明的 flagNamespaces/persistence;返回只读副本。
机制插槽
无。 Kernel 不提供改写准入判定、签名核验或装载管线的钩子。这是设计上的硬边界:任何「先放行再检查」的入口都会破坏 RPM 与 guard 的意义。
通知
| 名称 | 载荷 | 触发时机 |
|---|---|---|
kernel:on-unit-loaded | UnitInfo | 单元完成当前装载流程 |
kernel:on-unit-rejected | { id, code } | 单元被拒绝,或装载后核验失败;无法识别 ID 时为 "(未识别)" |
kernel:on-unit-unloaded | 同 kernel:on-unit-loaded | 单元已卸载,含装载后核验失败后的卸载 |
通知经 Cordis 广播,回调返回值不参与装载判定;广播会先复制订阅者列表,单个监听器同步抛错或异步拒绝只记录告警,不阻断其他监听器。flagNamespaces 随单元卸载释放声明,但不删除已保存的旗标或归属登记;core/sui 保留给框架。
实现位
| 名称 | policy | 基数 | 卸载行为 | 说明 |
|---|---|---|---|---|
kernel:pack-reader@1 | public | multi | vacant | 按包形态分派;内置 directory、zip、builtin,实现名即形态名 |
第三方可按新形态名新增读取器,经 Cordis 服务代理调用 register,并在清单 registers 中声明同一插槽与实现名;重名登记被拒绝。三个内置条目不随扩展卸载,第三方形态撤销后该形态不再可用。
关键语义
一次装载的顺序
load(ref)
├─ 经 Cordis 读取 host:backend@1,固定本次装载后端
├─ 读取包来源(directory / zip / builtin)
├─ 机械校验清单、入口与文件表
├─ 归档 hash 与签名核验
├─ guard 准入:四档判定、policy、写边声明
├─ 需要用户裁决时保持等待,答复后重读信任快照并重新判定
├─ 创建能力对象(按清单必需/可选)
├─ 执行代码入口(单文件 ESM)
├─ 挂载后核对实际写边与 `registers` 声明
└─ 广播 kernel:on-unit-loaded分发包内各单元分别判定,互不沿用结果;每个单元返回自己的 LoadOutcome。成功为 { status: 'loaded', id, kind, notices },拒绝为 { status: 'rejected', id?, code }。
等待、冲突与卸载
- 待用户裁决不是最终结果:
load保持等待,收到答复后重新判定。 - 同一 ID 已装载或正在判定时,拒绝码为
LOADER_VERSION_UNSATISFIED。 - 单元正在装载(判定、等待裁决、执行入口或挂载)时调用
unload,本次装载在下一个检查点撤销,以LOADER_UNLOADED结束,且不发kernel:on-unit-loaded。 unload(id)撤销该单元作用域;ID 不存在时直接完成。
写边核验
代码单元的 apply 拿到的是按清单裁剪过的上下文:
intercept(名)的名字须在writes.intercepts内;- isolate 域内
provide(名)的名字须在writes.isolateProvides内; - isolate 域之外的
provide一律报错,单元不能在公共命名空间新增服务; - 未声明的调用抛出
SlotError; - 声明了却没有发生的写边,同样判为不符并卸载单元。
子插件以及经 isolate、intercept 派生的上下文继承这层裁剪;经 ctx.root 等途径绕开的调用不在此列。
外壳控制面
boot 经 KernelConfig.onControl 接收 KernelControl。该对象不注册为扩展服务,单元无法经 Cordis 取得。
代用户选择实现也只属于控制面:实现位的 select、界面实现位的 choose/restore 只允许根上下文与框架本体调用。扩展单元经服务代理调用会被拒绝,不能通过清单声明取得这项权限;同 realm 代码的流程约束仍不等于安全沙箱。
| 方法 | 参数与结果 |
|---|---|
load(ref) | ref 为 { form, source },返回 LoadOutcome[] |
unload(id) | 撤销单元作用域;ID 不存在时直接完成 |
registrySources(registry, entryId) | 写过该条目的全部包(名称与版本,按叠加顺序);只给控制面 |
implSource(slot, name) | 查询实现由哪个已装载单元登记,供设置界面显示来源;框架本体登记返回 undefined,只供控制面使用 |
updateSigningList(input) | 交给信任绑定核验;接受返回 true,序号不大于当前清单返回 false,格式/签名/存储异常拒绝 Promise |
units()、pending() | 与 kernel:loader 相同的查询 |
report() | { unitId, code, at } 列表,at 为 UTC 毫秒 |
observations() | 根上的未知子树记录与 CSP 动态代码拦截记录;只表示观测,不代表已阻止 |
包来源与核验
| 形态 | source 的形状 |
|---|---|
builtin | { files: Record<相对路径, Uint8Array> } |
directory | list() 返回 { path, size, modifiedAt?, owner? }[];read(path) 返回文件字节 |
zip | { size, read(offset, length) } 随机读取;只接受 store/deflate,拒绝分卷、ZIP64、加密、路径穿越、重名项与越界内容 |
三种形态都返回 PackReader,提供 form、files()、read(path) 与可选的 sourceInfo()。目录文件的所有者与修改时间逐文件记录;外部归档的来源信息单独传递,不冒充内部条目信息。修改时间用 UTC 毫秒,宿主查不到的信息缺省。
Tauri 宿主的目录读取在宿主侧按 offset/length 分块,每块最多 64 MiB;前端拼接前核对读取期间文件大小未改变,变化即拒绝。Tauri 包列表最多 10,000 个;包句柄连续 24 小时未使用,会在下一次登记时失效。
registry 挂载或卸载时只增量重算受影响的条目,未受影响的生效值复用;条目顺序、合并与校验、交叉检查和通知语义与全量重算一致。
代码单元只许一个打包后的单文件 ESM 入口:code/ 下只能有清单 entry 指向的文件,多出文件或入口不在文件表内即 LOADER_MANIFEST_INVALID;包内模块之间的相对 import 不提供。归档 hash 取全部文件(含清单与签名文件)的相对路径与 SHA-256 表,经 JCS 后再计算 SHA-256,文件时间与重打包方式不参与。
核验后的读取器每次交付字节前复制并与私有哈希快照比对。核验后内容变化时,签名单元以 GUARD_SIGNATURE_INVALID 拒绝,未签名单元以 LOADER_MANIFEST_INVALID 拒绝;后续执行拿到的字节不再与来源缓冲区共用可变存储。
信任与能力
- 官方清单用 Ed25519 签名,序号单调递增;有效根签名且序号更大的清单自动接受并持久化,重启重新验签。
- 撤销只拒绝新装载,已装载单元继续运行;更高序号的清单可以恢复密钥。
- 清单过期时沿用名单并返回提示;个人批准仍须信任页内的用户操作。
- 能力按单元清单与宿主可用性判定:必需能力缺失则拒绝,可选能力缺失产生降级提示,获准能力只在该单元作用域提供。
- 隔离专用能力
caps:raw-bytes与caps:fs-read只允许框架代隔离 codec 读取用户本次选择的输入,不向 Worker 提供能力对象;普通插件不可取得。caps:net-fetch当前不可用。
一次装载在开始时固定宿主后端;中途切换宿主或撤销后端登记只改变后续装载,已有单元不迁移、不重建能力对象。无法取得当前后端时,装载以 GUARD_INDETERMINATE 拒绝。
当前实现范围
已完成:
- RPM 生成与启动校验;目录/ZIP/内置三种包形态;guard 准入与原因码;Ed25519 签名清单与信任裁决;能力注入。
- 网页与 Tauri 宿主;内置外壳与信任页。
- 数据包接入内容 registry;资源包经
kernel:unit-files提供已核验读取;Asset 维护来源栈。 - 隔离 codec 的代理登记、路径与预算核对、产物整理与写出。
未完成/限制:
- 完整阶段出口与各平台覆盖仍以实际证据为准,不以单项通过代替全部验收。
- 插件与 Mod 和框架运行在同一 JavaScript realm,没有技术隔离;写边核验是流程约束,不是安全边界。
- 翻译器在自身 Worker 内已分配的内存不受框架限制,浏览器不提供单个 Worker 的内存上限。
- 输入输出预算不等于浏览器进程内存硬限额;取消不会强制中止来源适配器已经发出的外部读取。