Skip to content

fix(cli): serve 按阶段区分「organizations 包缺席」与「插件拒绝挂载」(#4818) - #4858

Merged
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-4818-split-organizations-import-from-mount
Aug 3, 2026
Merged

fix(cli): serve 按阶段区分「organizations 包缺席」与「插件拒绝挂载」(#4818)#4858
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-4818-split-organizations-import-from-mount

Conversation

@xuyushun441-sys

Copy link
Copy Markdown
Contributor

Fixes#4818

问题

packages/cli/src/commands/serve.ts 里,importFromHost('@objectstack/organizations')kernel.use(new mod.OrganizationsPlugin()) 共用同一个 try,于是插件在构造 / 挂载阶段抛出的任何错误都被当成「包加载不出来」上报:文案说 @objectstack/organizations could not be loaded,出路里给了 OS_ALLOW_DEGRADED_TENANCY=1;而该 env 已设时,更会被降级成一条 warning 并继续启动

这是两件解法相反的事:

事实解法OS_ALLOW_DEGRADED_TENANCY
包缺席装上它 / 改单组织适用(operator 明确接受能力缺席)
插件拒绝挂载按插件自己报的原因处理不适用

代价是实打实的:包明明在镜像里,日志却把人指向模块解析 / NODE_PATH / 依赖 prune;更糟的是那条逃生口会吞掉插件自己的拒绝,等于把插件在守的闸门搬到一个 env 变量上(ADR-0049「声明与执行不一致」)。

改动

哪个阶段抛错分类,拆成两段 try:

  • import 阶段失败 = 包缺席 —— 行为完全不变:同一条 ADR-0093 D5 文案、同一个 OS_ALLOW_DEGRADED_TENANCY=1 逃生口(仍可显式降级启动)。
  • 构造 / 挂载阶段失败 = 插件自己拒绝 —— 原样上报插件的错误(message,以及它自带的 code,通用打印、framework 不作解释、不猜原因),明说包已找到并加载、不必去查模块解析,声明 OS_ALLOW_DEGRADED_TENANCY 对这条路径不适用且设了也过不去,并保留 OS_TENANCY_POSTURE=single 这个诚实的替代;然后无条件 process.exit(1)

判据只看阶段,不看错误形状。 没有引入 err.code === 'MULTI_ORG_NOT_LICENSED' / isMultiOrgLicenseError 之类的 cloud 私有谓词:一来 framework 不该编码 cloud 的私有语义(每加一种拒绝理由都要跟着改,是分层违规);二来该包是 importFromHost 动态加载的,CLI 与它可能持有不同模块实例,instanceof 和具名 code 都脆。「谁抛的」是唯一不需要 framework 了解插件内部的切法。

ADR-0093 D5 的态度不变:要求了隔离就不能假装有,仍然拒绝启动。变的只是「为什么拒绝」和「告诉 operator 什么」。

测试

新增 packages/cli/test/serve-organizations-mount-failure.e2e.test.ts(放在既有的 serve-organizations-host-resolution.e2e.test.ts 旁边,复用同一套 spawn 真实 CLI 的 harness —— 这条路径只有真进程能覆盖)。fixture:一个 app 不装该包(import 阶段失败),另一个装一个构造函数就抛错的假包(挂载阶段失败,错误带 code)。

四个用例:

  1. import 失败 ⇒ D5 文案 + could not be loaded + 逃生口仍被提供,不启动;
  2. import 失败 + OS_ALLOW_DEGRADED_TENANCY=1 ⇒ 正常启动并打上 DEGRADED TENANCY 烙印(行为不变,钉住防回归);
  3. 挂载失败 ⇒ 原样输出插件的 message 与 code、没有could not be loaded、退出;
  4. 挂载失败 + OS_ALLOW_DEGRADED_TENANCY=1仍然退出(本 issue 的要害:逃生口不再吞掉插件的拒绝)。

反向验证(把 serve.ts stash 回改前再跑):3、4 失败,1、2 通过 —— 正好说明新用例是有承重的,且「包缺席」那条路的行为确实分毫未动。

# 新用例(改后)
Test Files 1 passed (1) Tests 4 passed (4)
# 反向验证(改前的 serve.ts)
AssertionError: the mount refusal was misreported as an absent package
AssertionError: OS_ALLOW_DEGRADED_TENANCY swallowed a plugin refusal and served traffic
Test Files 1 failed (1) Tests 2 failed | 2 passed (4)
# 全量
pnpm --filter @objectstack/cli test -> Test Files 68 passed (68) Tests 590 passed (590)
pnpm --filter @objectstack/cli typecheck -> exit 0
eslint(改动的两个文件) -> 干净

已附 changeset(.changeset/serve-organizations-mount-vs-import.md,@objectstack/cli patch),内含唯一的行为变化说明与迁移建议。未触碰 content/docs/releases/

关联

  • objectstack-ai/cloud#1020(许可闸门归位插件;cloud 侧已用「在 app config 里更早拒绝」绕开,本 PR 不阻塞那边)
  • ADR-0093 D5(降级即拒绝启动)、ADR-0049(声明与执行不一致)、cloud#1013(host 解析)

🤖 Generated with Claude Code

https://claude.ai/code/session_018iARDqtrhQgz6fVHDeDkbQ


Generated by Claude Code

…4818)
`os serve` ran `importFromHost('@objectstack/organizations')` and
`kernel.use(new mod.OrganizationsPlugin())` inside one `try`, so an error the
plugin threw while constructing or mounting was reported as
"@objectstack/organizations could not be loaded" — an absent package — offered
OS_ALLOW_DEGRADED_TENANCY=1 as the way out, and, when that was already set, was
downgraded to a warning and the boot continued.
Those are two facts with opposite remedies. Split into two stages:
- import fails => package ABSENT => unchanged ADR-0093 D5 message and escape
hatch;
- construct/mount fails => the plugin itself declined => report its error
verbatim (message + any `code`, printed generically, never interpreted), say
the package WAS found so nobody chases module resolution, state that
OS_ALLOW_DEGRADED_TENANCY does not apply, and exit(1) unconditionally.
Classification is by which stage threw, never by the error's shape: the package
is loaded through `importFromHost`, so CLI and plugin may hold different module
instances, and the framework must not encode the plugin's private refusal
semantics.
D5's posture is unchanged — a deployment that asked for isolation still refuses
to boot without it. Only the diagnosis, and the escape hatch's reach, change.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018iARDqtrhQgz6fVHDeDkbQ
@vercel

vercelBot commented Aug 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 3, 2026 10:22am

Request Review

@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Aug 3, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli.

20 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/skills-reference.mdx(via packages/cli)
  • content/docs/api/client-sdk.mdx(via @objectstack/cli)
  • content/docs/api/data-flow.mdx(via @objectstack/cli)
  • content/docs/api/environment-routing.mdx(via @objectstack/cli)
  • content/docs/api/error-catalog.mdx(via @objectstack/cli)
  • content/docs/automation/hook-bodies.mdx(via packages/cli)
  • content/docs/deployment/backup-restore.mdx(via @objectstack/cli)
  • content/docs/deployment/cli.mdx(via @objectstack/cli)
  • content/docs/deployment/self-hosting.mdx(via @objectstack/cli)
  • content/docs/deployment/validating-metadata.mdx(via packages/cli)
  • content/docs/getting-started/your-first-project.mdx(via @objectstack/cli)
  • content/docs/kernel/runtime-services/data-service.mdx(via packages/cli)
  • content/docs/kernel/runtime-services/index.mdx(via packages/cli)
  • content/docs/permissions/authentication.mdx(via @objectstack/cli)
  • content/docs/plugins/index.mdx(via @objectstack/cli)
  • content/docs/plugins/packages.mdx(via @objectstack/cli)
  • content/docs/protocol/kernel/plugin-spec.mdx(via @objectstack/cli)
  • content/docs/protocol/kernel/realtime-protocol.mdx(via @objectstack/cli)
  • content/docs/releases/implementation-status.mdx(via @objectstack/cli)
  • content/docs/releases/v16.mdx(via @objectstack/cli)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@xuyushun441-sys@claude