feat(core,runtime): plugin ordering is a declared, kernel-enforced contract (ADR-0116, #4131) - #4163
Merged
Merged
Conversation
…ntract (ADR-0116, #4131) The kernel resolves init/start order from the plugin dependency graph, so kernel.use() registration order proves nothing — yet a plugin that needs a service at init when its provider is composed, while also booting without the provider, had no way to declare that. AppPlugin was the standing example (#4085: manifest grabbed in init before ObjectQLPlugin registered it, correct only by slot convention). - Plugin contract gains optionalDependencies (order-if-present), requiresServices (validated pre-Phase-1 and immediately before each init, with named structural errors), and providesServices (powers the validation and diagnostics). - One shared topological sort in plugin-order.ts replaces the two byte-identical copies in ObjectKernel and ObjectKernelBase. - A getService miss during Phase 1 now names the initializing plugin and the composed provider, so even undeclared plugins fail diagnosably. - AppPlugin declares the engine soft dependency + the manifest init requirement (cleared on the empty-env no-op path); ObjectQLPlugin and MetadataPlugin declare what their inits register. - ADR-0116 records the decision; lifecycle.mdx documents the contract; spec contract + validator schema carry the new fields; regression test boots a real kernel in the exact #4085 composition. Closes #4131 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014DQuJBNpwStpJvo3owBw2B
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 6 package(s): 126 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014DQuJBNpwStpJvo3owBw2B
os-zhuang
marked this pull request as ready for review
July 30, 2026 13:52
This was referenced Jul 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #4131(#4085 的结构性根因)。按 issue 中倾向的方案落地:方案 1 的排序语义 + 方案 2 的校验部分,并配 ADR(ADR-0116)。
内核契约(
@objectstack/core)Plugin接口新增三个字段,两个内核(ObjectKernel/LiteKernel)通过同一份实现(新的plugin-order.ts,同时消灭了两份逐字节相同的resolveDependencies拷贝)执行:optionalDependenciesdependencies一样拓扑提前(真实边,参与环检测);未组装则静默跳过,不报错requiresServicesService not found的位置,零误报)providesServicesprotocol)不得声明没有声明的插件也拿到诊断:Phase 1 期间的
getServicemiss 现在会附上「哪个插件正在 init、该服务由哪个已组装 provider 声明、该怎么声明依赖」。Service '<name>' not found前缀与 factory 路径的is async - use await逐字保留(有消费方做字符串匹配)。刻意不做:从服务声明自动排序(D4)——排序只来自
dependencies/optionalDependencies,服务声明只驱动校验与报错;声明完备度建立起来之后再议。首批声明方
AppPlugin:optionalDependencies = ['com.objectstack.engine.objectql']+requiresServices = ['manifest'](empty-env 无操作路径上清空,engine-less 内核照常启动)。objectql刻意不进requiresServices—— init 里对它的每次取用都在 try/catch 后面,属于 degrade 而非硬需求。ObjectQLPlugin:providesServices = ['objectql','data','manifest','lifecycle'](排除 option-gated 的protocol/objects/analytics)。MetadataPlugin:providesServices = ['metadata']。回归测试
app-plugin.ordering.test.ts用真实内核摆出 #4085 的原始组合(AppPlugin 注册在 engine 之前):现在任何 slot 都能正确启动;真正缺 manifest provider 的组合在 init 副作用之前指名失败。其它
docs/adr/0116-plugin-ordering-declared-contract.md):完整决策记录,含「一次是意外,两次是缺少被强制的契约」的历史与 rejected 方案 3(document + lint)。serve.ts/standalone-stack.ts的两处排序注释更新为「已声明、已强制」的现状(fix(cli,runtime): a named artifact that is missing is a broken instruction, not an empty boot — and the ordering claims say what the kernel resolves (#4110 follow-up, #4131) #4138 已修的注释保持不动)。contracts/plugin-validator.ts接口 +kernel/plugin-validator.zod.tsschema 增加同名字段;content/docs/protocol/kernel/lifecycle.mdx增加 Plugin Ordering Contract 一节;生成物 gate 全绿(check:docs/check:api-surface/check:authorable-surface等 8 项 PASS)。验证
pnpm test通过(turbo 71 任务全绿,exit 0);重点包单独跑过:core 437+19 新、objectql 1208、metadata 279、runtime 935+3 新、cli 881、spec 272 文件。pnpm build71/71 成功。🤖 Generated with Claude Code
https://claude.ai/code/session_014DQuJBNpwStpJvo3owBw2B
Generated by Claude Code