feat(core): init 阶段取服务的声明契约改为强制 — check:init-service-contract 门禁 (#4471) - #4631
Merged
Merged
Conversation
…ate (#4471) ADR-0116's ordering contract (dependencies / optionalDependencies / requiresServices / providesServices) was complete but voluntary: a plugin resolving getService('X') during init() while declaring nothing was invisible to every check, failing only under unlucky composition orders — the #4085 and #4420 failure class, the latter at data-consistency cost. - scripts/check-init-service-contract.mjs: AST scan of every plugin unit (classes and object literals) under packages/. Walks the init() call graph transitively (same-class methods, same-file functions — the #4420 call sat in a private helper), skips deferred callbacks (hooks) and start(), and errors on any init-reachable getService of a workspace-provided service not covered by a declaration. 12-case --self-test pins the #4420 pre-fix shape caught, all declared shapes passing, and the start()/hook shapes exempt. - Wired as check:init-service-contract in root package.json and lint.yml. - Declares the 11 previously undeclared init-time consumers the gate found (metadata, rest, two cli serve plugins, analytics, datasource-admin, job, knowledge, queue, settings, storage) via optionalDependencies on their providers — declared tolerance in the plugin, never a checker-side ledger. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012C2cd7tL8QDoZ2QKN3djJ5
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 11 package(s): 53 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
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.
Fixes #4471
问题
ADR-0116 /
packages/core/src/plugin-order.ts的顺序契约机制是完备的(dependencies硬依赖、optionalDependencies有则排前、requiresServices+assertInitServiceRequirements具名报错),但所有声明都是自愿的:一个插件在init()里getService('X')却什么都不声明,没有任何一层会指出来——只会在某个组合顺序下静默拿不到服务,而 miss 又通常藏在 best-effort 的 try/catch + warn 里。#4085 和 #4420 都是这么发生的,后者的代价是数据一致性级别(每次重启丢光在途审批)。方案(issue 期望的方向一:静态
check:*门禁)scripts/check-init-service-contract.mjs— 用 TypeScript AST 扫描packages/下所有插件单元(class 形态与对象字面量形态都覆盖),规则:getService('X'),且 X 由某个 workspace 插件的providesServices声明提供,消费方必须在dependencies/optionalDependencies里提到该 provider,或在requiresServices里提到该服务——否则报错(确定性地在 CI 挂掉,而不是概率性地在某次 boot 挂掉)。getService('manifest')在 init 调的私有方法registerRunObject里、还包着 try/catch——只匹配 init 正文的检查恰好漏掉真正出过事的形态。扫描从 init() 出发,传递跟随同类方法调用与同文件函数调用。start()阶段的消费(apps/setup/studio/account的低风险模式,issue 明确说不必按同一把尺子量)、无 workspace provider 的服务(无从声明顺序)。optionalDependencies(有则排前、缺则按声明降级,即 fix(automation,approvals): 审批决策不能在流程原地不动的情况下"成功" (#4420) #4460 的参考写法),checker 里没有任何豁免清单。--self-test(CI 中先于审计运行):pin 住 [automation/approvals] 进程重启后审批决策静默失效:挂起 flow run 仍只存内存(#1518 标记 COMPLETED 但 17.0.0-rc.1 未生效),approve 落库却永不推进且零报错 #4420 修复前形态被抓、已声明的各形态全部通过、start()/hook 形态不误报、对象字面量插件也在覆盖内、递归 helper 终止等。接入:根
package.json的check:init-service-contract+lint.yml新步骤;plugin-order.ts头注释补充"声明不是自愿的"一段,指向该门禁。门禁在现有代码上的初次收获(修复前全树 11 处未声明,全部在本 PR 补上声明)
首次运行即抓出 11 处未声明的 init 期消费——正是 issue 说的"少数漏网的那个最贵":
@objectstack/metadataMetadataPluginmanifestoptionalDependencies: [objectql]@objectstack/restrest-api-pluginmanifest(注册 sys_import_job)@objectstack/cliserve.ts 两个内联插件http.server/http-serveroptionalDependencies: [com.objectstack.server.hono]data(auto-bridge 探测)optionalDependencies: [objectql]manifest(Setup 导航)manifest+clusteroptionalDependencies: [objectql, service.cluster]objectql(RLS 复查)optionalDependencies: [objectql]manifest(注册 sys 表)每一处都保持原有的"缺 provider 时按设计降级"语义不变——变化只是:两者同时组合时,内核现在确定性地把 provider 排在前面,不再靠注册顺序的运气。
验证
node scripts/check-init-service-contract.mjs --self-test→✓ self-test: 12 cases(含 [automation/approvals] 进程重启后审批决策静默失效:挂起 flow run 仍只存内存(#1518 标记 COMPLETED 但 17.0.0-rc.1 未生效),approve 落库却永不推进且零报错 #4420 修复前形态被抓的 pin)✓ init-service contract: 41 declared / 1 self-provided / 3 without a workspace provider (73 plugin unit(s) scanned)pnpm turbo typecheck --filtercore/metadata/rest/cli/services → 64 tasks successfulpnpm turbo test --filtercore/metadata/rest/services → 37 tasks successful(rest 554 tests 全过);cli → 54 tasks successful范围外
issue 里"另外值得单独定一条"的日志级别约定(best-effort 降级导致"看起来正常、实则不持久"时不应是 warn)未在本 PR 实现,已另开 issue 跟踪(见 PR 评论关联)。
🤖 Generated with Claude Code
https://claude.ai/code/session_012C2cd7tL8QDoZ2QKN3djJ5
Generated by Claude Code