ADR 0047: 独立 Plugin 项目与 Root 生命周期域
状态
Accepted;全新项目目标架构。
背景
Plugin 是 Zhin 的基础运行单元,而不是只能被某个固定应用加载的扩展。A 可以作为 Root 独立运行,也可以作为 X 的 child;B/C 同理。整棵 Plugin tree 必须只有一个生命周期权威,否则配置、HMR、共享 Resource 和关闭顺序会出现竞争。
决策
D1. 每个 Plugin package 都是可运行项目
任意符合 canonical package contract 的 Plugin 都可以直接启动:
<plugin-project>/
├── package.json
├── pnpm-workspace.yaml
├── plugin.ts
├── schema.json
├── packages/
├── plugins/
├── pages/
├── commands/
├── components/
├── middlewares/
├── agents/
├── skills/
└── tools/packages/* 与 plugins/* 只允许一级 workspace package;逻辑 child tree 由静态 package manifest 表达,不使用 nested workspace。具体规则见 ADR 0048。
zhin dev .
zhin run @scope/plugin-aRuntime 读取同一个 plugin.ts definition 并把它实例化为 Root,不要求 Plugin package 编写另一份应用入口。测试 harness 也通过相同 public Interface 启动该包。
“可独立运行”不表示所有外部依赖都变成可选。Plugin definition 必须声明 required Resource/Feature;Root Bootstrap 提供标准 Resource,缺失的领域依赖在 setup 前给出结构化诊断。
D2. Root 是实例角色
interface PluginInstance {
readonly id: PluginId;
readonly parent?: PluginInstance;
readonly root: PluginInstance;
readonly role: 'root' | 'child';
}parent === undefined的实例是当前树的 Root。- package name、Plugin definition type 和 Root role 相互独立。
- 同一个 package 可以在一棵或多棵树中产生多个实例。
- Plugin 代码不得根据 package identity 假设自己是 Root。
D3. RootController 是唯一生命周期权威
只有 Root 持有 RootController,其 interface 覆盖整棵树:
interface RootController {
readonly generation: number;
start(): Promise<void>;
transact(change: TreeChange): Promise<Generation>;
reload(target: PluginId | CapabilityIdentity): Promise<Generation>;
stop(reason: StopReason): Promise<void>;
}RootController 负责:
- Plugin topology resolution 与 mutation serialization。
- Environment、ConfigStore、Database、Logger 等 Root Resource。
- Effective Schema、ConfigView 与 Capability snapshot 的原子 generation。
- Server/Client Module Runtime 和 HMR transaction。
- Runtime admission、quiesce、drain、cancel 与 shutdown。
- children-first dispose 和 Root Resource 最终回收。
Child Plugin 可以通过受控 command 请求装载、卸载或重载,但不能直接更新 parent children、全局 registry 或 generation。
D4. 生命周期是整树状态机
状态与顺序:
resolving:解析 Plugin definitions、children 和 required dependencies。configuring:组合 schema、物化默认值、校验配置。preparing:建立 shadow scopes 与 Module Runtime generations。starting:setup/start parent-first。ready:children-first 确认后一次发布整树 generation。quiescing:Root 阻止新任务,等待进行中的 Runtime lease。stopping:dispose children-first,Root Resource last。stopped或failed:不再发布 snapshot。
启动失败按已完成步骤逆序回滚,不发布部分 ready 的树。
D5. Root promotion 不改变 Plugin 实现
同一个 A package:
作为某棵树中 X 的 child:
id = root/x/a
config = plugins.x.a
page = /x/a/p-overview
作为 Root:
id = root
config = plugin
page = /p-overviewA 只通过 plugin.id、ConfigView、Resource Token 和 route helpers 获得运行时位置,不硬编码上述绝对路径。B/C 的 config、route 与 nav 自动相对新的 Root 重新投影。
D6. Host 不成为隐藏 Root
Host 负责进程能力:创建 Module Runtime、接收 OS signal、选择要启动的 Plugin definition。Bootstrap 完成后,Host 把控制权交给 RootController。
Host 不保存第二份 Plugin registry,不跨过 Root 直接 stop child,也不在 Root 之外提交配置或 HMR generation。若一个进程启动多棵树,每棵树具有独立 RootController、ConfigStore namespace 和 lifecycle domain。
不变量
- 任意 canonical Plugin package 都可作为 Root 启动。
- Root 是 Plugin instance role,不是特殊 package superclass。
- 每棵 Plugin tree 只有一个 RootController 和 generation authority。
- Child 不直接改变树、共享 Resource 或 lifecycle state。
- ready generation 整树发布,失败不暴露部分启动状态。
- Root promotion 只改变运行时投影,不改变 Plugin 实现。
后果
正面
- 每个 Plugin 都能单独开发、预览、测试和部署。
- 任意子树根都可以提升为独立应用。
- 组合部署与独立部署使用同一份源码和 package。
- 生命周期、配置、HMR 和资源回收拥有唯一权威。
设计成本
- Plugin 不能依赖隐式 parent/global singleton。
- Root Bootstrap 必须能提供标准 Resource 并诊断 required dependency。
- Config、Page route 和日志身份必须通过 role-relative view 获取。
- 多 Root 进程必须隔离 namespace、generation 和 Host 资源配额。