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

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 3, 2026 10:22am

Request Review

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

github-actions Bot commented Aug 3, 2026

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.

@xuyushun441-sys
xuyushun441-sys marked this pull request as ready for review August 3, 2026 10:26
@xuyushun441-sys
xuyushun441-sys added this pull request to the merge queue Aug 3, 2026
Merged via the queue into main with commit 29326f8 Aug 3, 2026
21 checks passed
@xuyushun441-sys
xuyushun441-sys deleted the claude/issue-4818-split-organizations-import-from-mount branch August 3, 2026 10:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants