Host API v0.1
状态:正式支持(Supported)。生产宿主通过封闭版本握手、两类绑定边界校验、完整兼容性测试和发布门禁后,公布
0.1: supported。
阅读方式
本页只负责版本级约定和模块索引。每个 Host API 模块都有独立文档,方法、事件、应用场景和用法示例以模块文档为准。
示例统一使用逻辑 JS 绑定对象 host:
const host = await appist.host({ apiVersion: '0.1' });
该入口由 @appist/host-api 提供,并在密封 guest 内自动连接宿主 transport;应用不能自行伪造或替换 transport。原生 WASM 示例使用 C 风格伪代码表达同一逻辑契约;ptr、len 和线协议编码只属于具体绑定,不属于 Host API 语义。
设计不变量
v0.1 以“最小且闭合的能力基线”为目标,而不是追求模块或便利方法最多。所有成员共同遵守:
- 版本先于成员:应用与宿主先协商精确版本;一个版本是封闭整体,不存在未命名的“部分支持”。
- L1 只承载宿主独占能力:只有真实资源、外部权限、系统集成或受控平台服务进入 Host API;可由 guest 基于既有能力等价实现的领域逻辑留在 L2。
- 没有证据就不冻结:缺少具体应用、端到端原型或边界结论的内容只进入非正式提议,不预占正式名字。
- 一种输入只有一种可观察语义:结构封闭,默认值、顺序、并发、部分结果、幂等性和终止条件必须可实现为一致性测试。
- 授权沿资源来源闭合:直接方法由 manifest 声明;后续操作只能从类型化句柄的资源、权限、来源与生命周期派生,不能靠无类型 token 旁路。
- 容量、时间与失败都有边界:数据、队列、活动资源和调用速率都受版本预算约束;长等待显式可取消,背压和溢出不得静默。
- 事实与策略分离:channel、来源、对端、宿主中介、物理效果和授权事实如实记录,信任档位由同一纯规则计算,不为期望结果反填事实。
- 逻辑契约跨绑定一致:JS 与原生 WASM 使用同一方法、事件、错误和生命周期;指针、长度与编码只属于 ABI,不产生另一套 API。
任何成员若不能满足这些不变量,应从 v0.1 移回非正式提议,而不是靠含糊措辞保留。
正式模块
v0.1 收录 9 个 L1 模块。这里的“收录”表示接口边界明确、两类绑定均已实现,并受发布端到端门禁保护:
| 族 | 模块 | 典型场景 | 参考文档 |
|---|---|---|---|
| 呈现 | canvas |
2D 绘制、软件像素帧、文本度量 | canvas |
| 呈现 | asset |
读取包内资源、注册包内字体 | asset |
| 呈现 | playback |
播放应用生成的 PCM 音频 | playback |
| 输入 | input |
指针、键盘、滚轮和焦点 | input |
| 输入 | ime |
系统输入法和合成文本 | ime |
| 输入 | capture |
摄像头帧和麦克风采样 | capture |
| 数据 | store |
应用作用域持久化 KV | store |
| 数据 | clipboard |
系统剪贴板读写 | clipboard |
| 平台集成 | env |
粗化环境快照和变化通知 | env |
八族只是分析工具,不要求首版覆盖每一族。不在上表中的现有方法不构成 v0.1 承诺;候选能力见非正式提议。
版本声明与协商
应用 manifest 必须声明精确版本、直接授权的方法、需要接收的事件,以及应用成立所需的最低预算:
{
"apiVersion": "0.1",
"canvases": { "main": { "mode": "draw" } },
"capabilities": ["host:canvas.draw"],
"events": ["canvas.frame", "canvas.resize"],
"apiLimits": {
"canvas.maxBatchOperations": 4096
}
}
capabilities 只列 authorization: declared 的方法。由句柄权限派生的方法(例如 capture.stop)不得再次声明;拥有有效句柄就必须能使用该句柄允许的操作。events 是兼容性和事件路由声明,不是另一份资源授权。使用 canvas、input 或 ime 时,canvases 必须把每个绘图面固定为 draw 或 pixels;使用 asset 时,assets 必须把稳定的 assetId 映射到规范化包内路径。包内路径由 / 分隔,每段只能含 ASCII 字母、数字、.、_、~、-,且不能是 . 或 ..;不接受百分号编码或由 URL 解析器再次规范化的别名。两张表都由发布器校验并作为整棵结构不可变的启动请求交给宿主,guest 不能在运行时扩充或改写。
宿主在 /_keel/capabilities.json 公布以下封闭结构;数值都是宿主对单个应用提供的上限:
HostCapabilities {
schemaVersion: 1
runtime: "web" | "native"
hostApi: {
versions: {
"0.1"?: {
status: "supported"
limits: { [K in V01LimitName]: u32[1..4294967295] }
}
}
}
}
V01LimitName 恰好是机器可读 catalog.json 顶层 limits 与各模块 limits 的并集;每个键必须出现一次,不允许未知键。每个值是宿主对单个应用保证可用的预算;应用 apiLimits 写成立所需的最低保证,因此要求值不高于宿主值。公布 0.1: supported 表示实现本版本全部 27 个方法和 15 个事件,不能用成员列表把它降格成“部分 v0.1”。暂未完成兼容性测试的宿主直接省略 0.1;draft 和 release-candidate 是文档/实现流程状态,不进入生产 advertisement。
发布器和启动器使用同一套校验,逐项确认:
- 精确的
apiVersion处于supported; - 所有声明方法和事件都属于 v0.1 catalog;
- 每个
apiLimits要求不高于宿主公布值; - 方法与事件声明符合来源依赖和句柄授权规则;
- 任一条件不成立时,在 guest 执行前以
version-mismatch失败。
apiVersion 与应用版本、contractVersion、二进制线协议版本和运行时构建哈希相互独立。
兼容性
- 只有明确标为 extensible 的结构可以增加可忽略字段;v0.1 当前定义的参数、返回值和事件载荷都是封闭结构。
- 新增模块、方法、必填参数或新的失败前提进入后续版本。
- 删除方法、改变参数含义、收紧既有合法输入或改变可观察副作用属于不兼容变化。
- 安全修复可以拒绝原本就违反预算、schema 或权限要求的调用。
- 0.x 不承诺 1.0 级长期稳定,但承诺版本内语义稳定。
共享类型
Bytes 有明确上限的小块字节
ByteSource 大块输入;JS 绑定 Uint8Array,原生 guest 绑定已校验内存区域
ByteSink 大块输出;方法返回实际 written 与该方法定义的完整性信息
Event 经始终存在的类型化事件传输送达
Handle<Resource, Rights, Provenance, Lifetime>
CallOptions { signal?: CancellationSignal }
方法在同步进入绑定时取得 ByteSource 的不可变快照;调用返回 Promise 后 guest 修改原缓冲不得改变本次输入。实现只有在能证明调用期间 guest 不可能并发修改时才能用借用代替复制。ByteSink 从同步进入绑定到调用结束由该调用独占;这段时间 guest 不得读写或交给第二个调用。宿主只修改成员页定义的 written 区间,失败或未写区域保持调用前字节不变。JS 绑定与原生绑定都必须执行同一所有权规则。
句柄是不透明值。每次使用都必须验证资源类型、权限、来源和有效期;宿主主动失效句柄时,若应用声明了相应终止事件,就按成员页保证发送。v0.1 不允许把一个模块产生的无类型 token 交给另一模块绕过权限判断。
字符串都是合法 Unicode 标量序列;标注为 UTF-8 字节长度的预算在编码后计算。整数不允许溢出,浮点数必须有限。数组、映射和嵌套结构都受宿主公布预算限制。
方法授权与事件来源
方法只有两种授权方式:
| 授权 | manifest | 调用条件 |
|---|---|---|
declared |
必须列出完整 capability | 发布和启动均通过声明检查 |
handle-derived |
禁止再次列 capability | 传入同一应用持有、类型/权限/来源/生命周期均匹配的有效句柄 |
事件必须列在 manifest 的 events 中,并按来源分两类。成员页所写的送达、次数和排序保证都以该事件已经声明为前提;省略声明只会抑制该事件的交付,不改变底层资源状态,也不授予宿主发送未声明事件的权力:
passive:只在应用可见、相关绘图面属于本应用且事件声明存在时发送;适用于canvas.*、input.*、env.change。handle:只有资源创建方法成功返回句柄后才发送,载荷必须带该句柄;关闭、撤销或终止后只允许再发送一次终止事件。
handle 事件继承 sourceMethods 可达方法中的最高档位;passive 事件继承本模块直接授权方法的最高档位。应用最终档位同时计入它声明的 passive 事件;在本版中这些事件均为 green。事件不能借“没有独立 capability”绕过定档。
事件传输在 guest 启动前由宿主建立,不要求 guest 预先同步注册回调。队列有界;只有成员页明确允许合并的事件可以合并。不得把事件声明当成获得摄像头、音频输出或 IME 会话的授权。
同一应用的统一事件传输按入队顺序 FIFO 交付;不同系统源同时发生时只保证各成员页明示的先后关系。runtime.maxQueuedEvents 是每个应用尚未交付事件的总上限。队列满时,宿主先按成员页规则合并可合并事件;仍无法容纳一个不可合并事件时必须以 event-overflow 终止当前 guest 会话,不能静默丢弃、覆盖旧事件或无限增长内存。该终止是运行时故障,不伪装成某次 Host API 调用的 HostError。
错误、取消与预算
失败统一使用:
HostError {
method: string
code: string
retryable: boolean
}
method 使用不带 host: 的规范方法名,例如 capture.frame;句柄派生方法也有稳定 method 名,但这不把它变成 manifest capability。相同 code 在不同方法中可以有不同恢复动作,guest 必须同时按 method 与 code 解释。
所有 27 个方法共有以下绑定级错误:invalid-argument 表示 guest 请求的结构、类型、编码或内存区域不符合契约;platform-unavailable 表示完成调用所需的平台基础设施暂时不可用;internal 表示宿主内部不变量失败且不得泄漏实现细节;protocol-error 表示绑定拒绝了宿主返回的成功值或事件,因为它不符合封闭 schema;transport-error 表示绑定无法可靠地完成与宿主的传输。版本选择失败单独使用 version-mismatch,不冒充某个方法的业务失败。各成员页的“特有错误”与这些公共错误取并集,构成该方法的完整封闭 code 集合;未列出的 code 不得穿过绑定。event-overflow 是会话终止原因,不是 HostError.code。
- 用户关闭系统选择或确认界面返回成员页定义的空值,不抛错;取消结果不得与成功混用。
- 权限拒绝使用
denied,版本冲突使用version-mismatch,预算超限使用limit-exceeded;底层临时不可用若已有成员特有码(例如device-unavailable)必须使用特有码,不能一律抹平成公共platform-unavailable。 - 只有成员页标为“可取消”的调用接受末尾
CallOptions。触发取消后以cancelled结束,不得返回部分句柄;已经交给外部系统的副作用不承诺回滚。 - 所有其他调用必须在其单次预算内完成,不得隐式转成长任务。
- 预算同时覆盖单次容量、应用活跃总量和时间窗速率;预算键和单位属于版本契约,具体数值由宿主公布。
应用声明的最低预算超过宿主上限时必须在 guest 启动前失败,不能运行后再靠随机的 limit-exceeded 暴露不兼容。
JS 绑定在启动握手后以 host.limits 暴露宿主公布的完整预算表。原生 WASM v0.1 绑定不复制第二份运行时预算表:原生应用必须把成立所需的最低值全部写入 manifest,并只按这些最低值分配缓冲和安排队列;宿主仍在 guest 启动前逐项校验。原生绑定不能据此假定宿主还提供了 manifest 未声明的更高余量。
CancellationSignal 是当前 guest 内的不可序列化取消状态。调用开始前已取消则不得产生任何副作用;调用中取消只影响绑定它的调用,不自动关闭已有句柄。JS 绑定接受 AbortSignal,原生绑定接受运行时 cancel token,两者映射到同一逻辑语义。
预算值在 JSON 中都是 [1, 4294967295] 范围内的 u32,避免逻辑类型与 JS/原生绑定出现不同上限。名称以 Bytes 结尾时单位是 8-bit byte;以 Ms 结尾时单位是毫秒;以 Frames 结尾时使用对应模块定义的媒体帧;PerSecond/PerMinute 表示任意滚动 1 秒/60 秒窗口内开始且获准执行的次数;maxStreams、maxSessions 等表示同一应用同时活动的资源数;runtime.maxQueuedEvents 表示统一事件队列中尚未交付的事件数。除成员页明示的最低持续保证(当前只有 store.listSnapshotIdleMs)外,预算都是不得超过的容量/次数上限;数值越大都表示宿主提供的能力不更弱。方法在校验失败前不得消耗速率计数,开始执行后无论成功、取消或外部失败都计一次。
固定信任档位
v0.1 的方法档位由随规范发布的 catalog.json 策略事实计算,结果只能是 green、lime 或 yellow。成员页必须写固定结果,禁止“满足某些条件时为 green”之类的条件档位:那些条件本身就是符合 v0.1 的强制要求;做不到的实现不得宣称支持该方法。
应用的最终档位取所有直接声明方法、它们可产生句柄所能到达方法,以及已声明 passive 事件中的最高档。handle 事件继承其来源方法的最高档,不另行改变结果。
L0 运行时原语
L0 不参与 capability 声明与信任定档:
| 原语 | 语义 |
|---|---|
log(level, ByteSource) |
有长度、速率和控制字符过滤的诊断日志 |
host_call |
发起一次类型化 Host API 调用 |
send_batch |
批量提交运行时消息 |
| typed event transport | 将 L1 事件送入 guest;不承载资源授权 |
now 与 random 尚未完成 v0.1 所需的实现和验证,不属于本版本。
明确不提供
v0.1 故意不提供原始 DOM、Canvas/WebGL/WebGPU context、任意 URL 资源加载、外部网络、平台身份、打印、像素读回、屏幕捕获、任意文件路径、原始 socket、WebRTC、P2P、bearer token、全局用户 ID、email、动态代码加载或任意 guest 着色器。
scene、数据库、codec、聊天会话等可在 guest 实现的领域模型属于 L2。缺席是 v0.1 安全契约的一部分,不能作为“遗漏”在补丁版本中加入。
与当前实现的关系
- 当前各模块普遍使用的
version: "1"是契约描述版本,不是 Host API v0.1。 - 当前
media、audio、text等旧名仍需迁移到capture、playback、ime。 net、identity、print的候选契约已移回非正式提议:当前生产宿主分别缺少地址绑定的网络代理、appId 作用域的 nonce 身份流程和隔离 PDF 解析器。- 当前
identity.current、billing.*、ai.chat、analytics.track不在 v0.1。 - 当前
asset.decodeBytes({fileToken})组合路径必须先完成安全整改。 - 当前两类 guest 的事件通路必须收敛到统一的类型化事件传输。
宿主只有在名称、逻辑签名、版本握手、策略和行为全部符合本版本文档时,才可以公布 apiVersion: "0.1"。