插件宿主生命周期、范围与数据
页面位置与业务作用
surfaces 只描述插件自带页面的位置(public / account / admin),不是插件业务作用或权限。OAuth 插件可只有 admin 配置页,但通过 zboard.identity.provider.v1 向核心提供身份验证能力;前台登录和注册入口由核心生成。这些是宿主协议与运行时约束,管理页只展示页面位置、业务作用、能力和生命周期结果。
宿主准入与核心策略
范围和授权由 ZBoard 自己保证。发布者在签名 manifest 中声明能力和页面位置;宿主安装器校验可信签名、完整性、平台兼容性、能力白名单与结构约束,自动生成绑定当前包摘要的准入记录。调用时继续检查具体能力、用户身份、surface、安装状态和 generation,核心业务还检查自身规则。管理员不需要逐项打勾授权,也不能通过接口绕开宿主白名单。
准入记录与安装版本、迁移数据在同一事务提交。更换包必须重新经过宿主校验;未知能力、越界配置页和不兼容包直接拒绝。没有手工授权 API,没有独立的“执行迁移”操作。历史 plugin_authorizations 表保留为宿主内部准入记录,原来的人工授权语义已取消;只读 API 字段为 admission。
当前允许的能力:
| 能力 | 宿主执行范围 |
|---|---|
zboard.ui.page.v1 | 声明的隔离页面;仍须通过 surface 身份校验 |
zboard.config.v1 | 自身配置、公开字段投影和诊断 |
zboard.identity.provider.v1 | 第三方身份断言;核心负责账户、注册和会话 |
zboard.storage.v1 | 自身加密 JSON 数据;没有数据库句柄、SQL 或核心表访问 |
有效能力必须同时满足当前包声明、宿主准入、当前调用身份、安装状态和核心策略。站点关闭注册后,未绑定身份的登录失败;关联账户不存在或停用也失败。已绑定正常账户可登录。同邮箱不代表绑定,不能通过邮箱自动接管账户。注册提交时再次检查站点开关,防止授权期间策略改变后仍创建账户。
原生程序仍只支持由部署方配置的可信发布者签名的包。当前不是操作系统沙箱,原生进程拥有部署账号的文件与网络权限;业务 API 的限制不能视为对恶意原生代码的强隔离。这个部署边界不能通过管理员勾选框变成安全保证。
宿主恢复现有安装时重新校验包和发布者,自动补齐历史安装的准入/数据准备,并按已提交启停状态恢复。校验失败不会运行插件,核心服务仍可启动;原因记录在安装状态,重新导入会重新执行完整准备流程。
生命周期事务
- 首次安装:验证签名、平台与能力 → 暂存不可变包 → 准备默认数据及迁移 → 原子提交版本、准入、配置、数据和迁移记录。安装成功后保持停用,业务参数由管理员正常配置。新安装失败不留下半成品安装或私有数据。
- 启用:检查准入、数据版本、历史迁移和当前包,启动并核验进程身份、健康与配置,成功后才发布页面/服务入口。已经正常启用的实例重复启用不会重启进程。
- 停用:宿主串行化自身调用,关闭活动状态、撤销会话并停止进程;保留数据。
- 升级:在旧版本仍为当前版本时准备迁移副本和候选进程;已有配置须通过新运行时校验。一个数据库事务切换版本、准入、配置、数据、迁移记录及 generation,随后切换宿主进程指针并关闭旧实例。升级成功保留原启停状态,失败保留旧版本、旧数据、旧实例和有效会话。宿主准备期间私有存储调用返回可重试的 503。尚未声明测试当前宿主的包不能直接替换运行中的插件,须先停用。
- 卸载:由宿主停止运行、撤销会话、删除程序和页面;默认保留配置和数据。无需管理员先停用来拼接生命周期。
- 恢复旧版本:目前要求先停用;宿主检查数据范围、迁移校验和和已有配置,成功后保持停用。不会自动向下迁移数据。
所有数据库写入受宿主租约约束。失败尝试写操作日志;成功安装的操作结果与版本切换同一事务提交。崩溃恢复只采用已提交状态。尚未提交的不可变包文件不会成为活动版本;它不是有效安装。
私有数据
宿主在 plugin_data 中按插件 ID 保存一个加密 JSON 对象。单插件最多 128 个键、256 KiB;单值最大 32 KiB。键允许 ASCII 字母、数字、下划线、点和横线,最长 128 字符。顶层键区分大小写。不存在动态创建的插件业务表,插件不能提供表名或 SQL。
每次写入递增整个命名空间的 revision;所有写入需带最近读取的 revision,冲突返回 409。请求中的命名空间来自已认证页面会话或原生进程私有连接,调用方不能传入其他插件 ID。原生进程只在当前实例处于启用状态且权限仍有效时可访问。
后台页面桥支持 storage.get / storage.put / storage.delete,请求字段为 key、revision、value(put 使用)。get 返回 { revision, found, value? };写入返回新 revision。公开页和用户前台不能访问这个管理员范围的私有数据接口。页面仍不能直接读取宿主令牌或发任意网络请求。
服务端 Go SDK:
store, err := pluginv1.HostStorageFromEnvironment()
if err != nil { return err }
defer store.Close()
current, err := store.Get(ctx, "cursor")
if err != nil { return err }
_, err = store.Put(ctx, "cursor", current.Revision, json.RawMessage(`{"offset":42}`))宿主通过专属 Unix socket 和短期进程 token 提供服务,不暴露管理端令牌。SDK 的调用应在普通后台工作中进行,不能在 Health、ValidateConfig、ApplyConfig、TestConfig 或身份验证等由宿主调用的生命周期 RPC 内同步回调存储。宿主事务期间返回 503,插件应退出当前回调后稍后重试;409 必须重新读取数据后决策。升级切换、停用、卸载、进程退出或重启会关闭旧连接。
声明式迁移
需要私有存储的包必须声明 data。只迁移自身配置也可声明 data,而不申请 storage 能力。版本链从 1 连续到当前 version,最多 32 步,总变更最多 128 条。历史步骤必须完整保留,不得修改已经发布的迁移。
{
"data": {
"version": 2,
"min_compatible_version": 2,
"migrations": [
{ "version": 1, "changes": [] },
{ "version": 2, "changes": [
{ "target": "storage", "operation": "rename", "key": "old_cursor", "to": "cursor" },
{ "target": "config", "operation": "set_default", "key": "batch_size", "value": 20 }
] }
]
}
}支持的 target 为 config / storage;分别要求对应的宿主准入能力。操作只允许:
set_default:键缺失时补值,已有值不覆盖。rename:源存在才移动;目标已存在则失败,避免覆盖数据。remove:删除该命名空间的一个键,属于显式破坏性变更。
宿主不接受 SQL、脚本或插件自定义迁移回调。原生插件仍通过既有配置 RPC 验证、规范化并应用候选配置,验证不通过时不提交数据;停用安装的候选进程完成后退出,运行中的升级在提交后接替旧实例。
初始化和前向迁移是安装/升级事务内部步骤,管理员只执行安装或升级。迁移失败使整个安装/升级失败,重新导入可重试。数据未准备完成时禁止启用和私有存储访问。版本页可查看数据状态和已提交记录,不能独立触发迁移来制造版本与数据错配。
配置、私有数据、版本号、generation 和迁移记录在一个宿主数据库事务内提交;中途失败全部回滚。操作日志记录失败,迁移台账只记录已提交步骤,包含数据周期、版本、包摘要、步骤校验和、操作者和时间。重启后按已提交状态恢复,未完成操作标记中断。
恢复旧程序前检查其声明的数据范围和历史迁移校验和。当前数据超过旧包声明版本时拒绝恢复;不自动执行向下迁移,也不声称旧程序包就是数据备份。数据恢复应使用一致的数据库、插件目录和加密密钥备份。
卸载与清理
- 停用:停止运行,保留配置和数据。
- 卸载:自动停止运行、撤销会话并删除程序和页面,保留自身配置、私有数据、版本和操作记录,以及核心用户、身份绑定和审计记录。
- 清除配置与私有数据(purge_data):仅在卸载后执行,清空自身配置与私有数据,数据版本重置为 0、数据周期递增。迁移/操作记录和核心数据保留;重新安装时由宿主在新周期自动初始化。
宿主自身的 0004_plugin_governance 迁移创建准入记录、私有数据和迁移台账;这是核心 schema 迁移,不能由插件安装/卸载执行。MySQL 版本化 SQL、SQLite 初始化和跨数据库迁移清单均包含这些表。清除插件数据不会删表。

