迁移到类型化插件配置
类型化实例配置对于所有读取操作器配置的 1.0 之前插件都是一项破坏性变更。本页面是面向插件作者和运维人员的迁移文档:哪些内容会中断、原因,以及修复软件包的确切步骤。
此处所述行为会根据 crates/zeroclaw-plugins/src/config.rs、crates/zeroclaw-plugins/src/instance.rs 以及 crates/zeroclaw-plugins/src/host.rs 中的准入路径进行检查。
发布决策
强制执行机制与此功能一同发布。没有兼容性垫片、宽限期或选择退出标志。插件在 1.0 之前属于实验性接口,因此项目选择接受这一破坏性变更,而不是永久保留一个更弱的配置路径:无类型回退机制必须将宿主无法确定类型、命名或设置边界的值交给 guest,而这正是此功能要堵住的漏洞。
未迁移的软件包将不再被发现。不会发生静默降级,也不会有不完整的配置传递给来宾代码。
会出现什么问题
三件彼此独立的事情:
- 请求
config_read但未包含config_schema的清单不再被发现或安装。 二者互为必要条件:没有该权限的架构同样无效。 - **不再读取以包名或绑定名称为键的配置项。**现在,运算符值存储在由包、能力和绑定派生的完整实例键下。
- 来宾接收的是类型化的 JSON,而不是字符串映射。 曾经自行解析字符串的来宾现在可以直接获得真正的布尔值、数字、数组和对象。
为什么宿主需要模式
操作员值以带有 secret 标记的字符串映射形式存储,并在静态存储时加密,而来宾是不受信任的第三方代码。在来宾启动之前,主机必须能够回答两个问题,但如果没有声明式契约,主机无法回答:此软件包获准接收哪些键,以及每个值的类型是什么。WIT world 是固定的,并由所有插件共享,因此每个软件包的配置类型无法放入 ABI。manifest 是唯一可以声明该契约的地方,而 additionalProperties = false 加上显式的 properties 映射,才使 config_read 授权具有可枚举的含义。
编写步骤
1. 声明架构
添加一个封闭的 Draft 2020-12 对象,准确涵盖插件读取的键。每个顶级属性都必须解析为一种明确的类型:string、boolean、integer、number、array 或 object。
name = "my-plugin"
version = "0.2.0"
wasm_path = "my_plugin.wasm"
capabilities = ["channel"]
permissions = ["config_read"]
[config_schema]
"$schema" = "https://json-schema.org/draft/2020-12/schema"
type = "object"
required = ["bot_token"]
additionalProperties = false
[config_schema.properties.bot_token]
type = "string"
minLength = 1
[config_schema.properties.poll_interval_secs]
type = "integer"
minimum = 1
[config_schema.properties.allowed_chats]
type = "array"
宿主会对架构本身强制实施以下限制:序列化后大小不得超过 64 KiB,嵌套层级最多 32 层,不得包含 $id,且 $ref 目标必须是本地 JSON 指针。远程引用会被拒绝,因此架构永远不会触发网络获取。
动态键关键字不属于此方言:patternProperties、propertyNames 和 unevaluatedProperties 在根级会被拒绝,因为宿主只会为根 properties 映射中列出的键具体化值,因此通过模式匹配允许的键永远不会传递到插件。嵌套属性模式不受影响。
pattern 使用线性时间正则表达式方言
宿主会在每次调用时通过重新编译并重新验证你的架构来解析配置,而这项工作在宿主上执行,不计入组件的燃料预算。因此,pattern 仅限于开销可由宿主预测的正则表达式:
- 反向引用和环视断言会被拒绝。
(\w+)\s\1、(?=...)、(?<=...)及其类似形式需要回溯匹配器。模式会改用线性时间的regex方言进行编译,无论模式如何编写,匹配所需的时间都与值的长度成正比。 - 单个模式编译后的程序大小不得超过 256 KiB。 大的重复次数会触发此限制:
^[\s\S]{0,200}$没问题,^[\s\S]{0,1000}$则不行。请使用maxLength设置长度限制;检查它无需额外开销,而且能准确表达你的意图。
这两次拒绝都发生在安装时,并显示指明架构的 InvalidManifest 错误,因此,对于主机无法限定其 pattern 的插件,根本不会运行。结构化模式的行为符合预期:slug、UUID、电子邮件地址、URL 以及长度受限的简短自由文本都可以编译。
2. 匹配值编码
Operator 存储仍为字符串映射。架构会告诉宿主如何读取每个存储的字符串:
| 声明的类型 | 运算符存储的内容 | 访客收到的内容 |
|---|---|---|
string | secret-value | "secret-value" |
boolean | true | true |
integer | 4 | 4 |
number | 0.5 | 0.5 |
array | ["a","b"] | ["a","b"] |
object | {"k":"v"} | {"k":"v"} |
任何无法解析为声明类型的内容,都会在代码运行前被拒绝。
3. 确定每个键是必需项还是可选项
最终授予的权限会与清单请求分开检查。当请求 config_read 但未获授予时,宿主会根据你的架构验证一个空对象:
- 全可选架构会接收
{},因此请为每个字段提供来宾端默认值。 required字段采用失败即关闭策略,这正是凭据所需要的行为。无法进行身份验证的通道应拒绝启动,而不是在配置不完整的情况下运行。
4. 在来宾端反序列化类型化 JSON
将字符串解析替换为对注入对象的一次反序列化。工具插件读取保留的 __config 键,主机删除模型提供的任何同名值后,将其合并到调用参数中。
5. 重新构建并重新签名
config_schema包含在清单签名中,因此添加该字段后,已签名的软件包必须重新签名。有关签名流程,请参阅分发插件。
操作步骤
现有的以软件包或绑定命名的 [[plugins.entries]] 块不会被读取。可用的迁移路径取决于插件能力。
工具实例
Install 和 info 命令可以从软件包的默认工具绑定中派生工具实例。要将工具值迁移到新键上:
- 运行
zeroclaw plugin info <package>可打印完整实例密钥,其格式类似zpi1_...。 - 将现有条目的
name重命名为该键,或重新安装插件以初始化该条目,然后使用zeroclaw config set plugins.entries.<instance-key>.config.<key>设置值。 - 保存配置。值在静态存储时保持加密。
该密钥是软件包、能力和绑定的带版本可逆编码,因此两个软件包可以同时使用名为 main 的绑定,而无需共享凭据。全新安装会自动生成并输出此工具密钥。
通道实例
渠道键包含已配置的渠道别名。zeroclaw plugin install 和 zeroclaw plugin info 知道包,但不拥有该别名,因此无法推导、打印或预置渠道键,也不得臆造包级替代项。支持别名的渠道构造和运行时配置解析已在 zeroclaw#10146 中落地:守护进程会构造一个显式声明的渠道实例,并根据实际配置的别名,从 zpi1(package, channel, alias) 解析其类型化配置。
频道实例的自动 plugin info 密钥显示和安装时预置,在 zeroclaw#9584 中的授权流程完成之前仍需手动进行。在该流程落地之前,运维人员需要手动使用 zeroclaw config set 写入频道密钥,而不是由安装或 info 为其打印并写入,因此,依赖自动安装和 info 密钥流程的仅频道软件包目前尚未完成。
诊断遭拒#+#+#+#+
| 消息 | 原因 |
|---|---|
请求 config_read,但未声明 config_schema | 第 1 步未完成 |
声明了 config_schema,但未请求 config_read | 移除架构或添加权限 |
config_schema 必须设置 additionalProperties = false | 根对象已打开 |
config_schema 不得在根级别声明 <keyword> | 根对象使用了 dynamic-key 关键字;请改为为 properties 中的每个键命名 |
| 属性使用了不受支持的类型 | 属性没有显式支持的类型,或本地 $ref 无法解析 |
config 中包含 config_schema 中不存在的属性 | 运算符键未声明,通常是拼写错误 |
| 配置属性必须是 JSON 整数 | 存储的字符串无法解析为声明的类型 |
config 在 <path> 处不符合 config_schema | 某个约束(例如 minimum 或 required)未通过 |
第一方软件包
zeroclaw-labs/zeroclaw-plugins 中发布的每个包都会请求 config_read,而在此变更落地时没有任何包声明 config_schema,因此它们全部需要执行第 1 步和第 5 步。迁移在该仓库中而不是此处跟踪,因为这些包的版本独立于宿主。工具包现在可以完成操作员密钥步骤。仅频道包必须等待上文支持别名的密钥路径,之后跟踪器才会根据此契约将其标记为已迁移或发布为已迁移。
内存插件
Memory 插件目前尚未支持配置导出,在该 ABI 存在之前不得请求 config_read。