迁移到类型化插件配置
类型化实例配置对于所有读取操作器配置的 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"
宿主会对 schema 本身实施以下限制:序列化后不超过 64 KiB,嵌套层级最多 32 层,根属性不超过 256 个,不允许使用 $id,并且 $ref 的目标必须是本地 JSON Pointer。远程引用会被拒绝,因此 schema 永远不会引发网络请求。超过任一限制的 schema 会在包安装/准入阶段被拒绝,并采用失败即关闭(fail-closed)的行为,且早于验证器编译。
动态键关键字不属于此方言: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#10146 中落地:守护进程会构建显式声明的频道实例,并基于实际配置的别名,从 zpi1(package, channel, alias) 解析其类型化配置。
绑定该别名会派生、打印并填充该密钥。要将通道实例的值放到其上:
- 运行
zeroclaw plugin bind <package> --channel-alias <alias>。如果清单声明了目标地址,且该包包含可访问这些目标的传输层(http_client、websocket_client或socket_client),并且该实例尚无对应行,则添加--egress declared以授予这些网络访问权限,或添加--egress none以绑定且不具备任何网络访问能力。若两者都不指定,命令将拒绝执行并列出这些目标。该命令会创建[channels.plugin.<alias>]绑定,或保留你已编写的绑定,并为该实例初始化[[plugins.entries]]行。zeroclaw plugin install <source> --channel-alias <alias>在安装时执行相同的操作。 - 使用
zeroclaw config set plugins.entries.<instance-key>.config.<key>设置值。绑定的就绪报告会列出每个required键的状态(已设置或缺失),对于缺失的键会给出对应的确切命令;zeroclaw plugin info <package>会输出实例键,并再次打印同样的报告。值在静态存储时保持加密。
A instance 键下已存在的行保持原样不变,因此你手动写入的值在绑定后依然保留。对于 channel 实例,不会查询任何旧名称:channel 构造晚于完整实例键的引入,因此没有任何运行时曾从以其包名或别名命名的行中读取 channel 配置。
诊断遭拒#+#+#+#+
| 消息 | 原因 |
|---|---|
请求 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 步。迁移工作在该仓库中跟踪,而不在此处进行,因为这些包的版本与宿主程序独立管理。两类包现在都可以完成操作员密钥步骤:工具包通过安装和 plugin info 完成,仅含通道的包则通过 zeroclaw plugin bind 完成,具体见上文。
内存插件
Memory 插件目前尚未支持配置导出,在该 ABI 存在之前不得请求 config_read。