Skip to content

fix(cli): 启动 banner 的 Tenancy: 改读 resolveTenancyPosture(),不再打印被取代的布尔 (#4801) - #4994

Merged
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-4801-banner-tenancy-posture
Aug 3, 2026
Merged

fix(cli): 启动 banner 的 Tenancy: 改读 resolveTenancyPosture(),不再打印被取代的布尔 (#4801)#4994
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-4801-banner-tenancy-posture

Conversation

@xuyushun441-sys

Copy link
Copy Markdown
Contributor

Fixes #4801

为什么读 posture 而不是布尔

ADR-0105 D1 之后,OS_TENANCY_POSTURE 是权威 knob,布尔 OS_MULTI_ORG_ENABLED 只是 resolveTenancyPosture() 在 posture unset 时的回退。serve运行时接线(serve.ts 里挂 @objectstack/organizations 的那段)早就读 resolveTenancyPosture(),但 banner 的 Tenancy: 行读的是 resolveMultiOrgEnabled() 返回的布尔 —— 同一个事实两个来源,于是它们漂了:

  Tenancy: single-tenant          ← banner 读布尔(OS_MULTI_ORG_ENABLED 未设 ⇒ false)
  Plugins: 40 loaded
           …, Organizations, …    ← 运行时按 posture=isolated 真的挂上了组织墙

这不是显示美化。这是「声明与执行不一致」(ADR-0049 那一类)落在诊断面上 —— 最坏的落点:一个可能撒谎的 banner,会让此后每一次排障都多花一轮去确认它有没有撒谎。cloud#1020 里就是靠人工比对插件表才发现的。

Tenancy: 现在直接打印 posture 名(single / group / isolated),来源是运行时接线用的同一个 resolveTenancyPosture() 调用。

布尔字段的去留:删除,不保留兼容项

ServerReadyOptions.multiTenant?: booleantenancyPosture?: TenancyPosture。理由有三条,按重要性:

  1. 说不清它还为谁存在。 posture 是权威源;若保留布尔,议题已经规定「不一致时以 posture 为准」—— 那它就是一个永远只能被忽略的字段。一个存在但不可信的字段,正是本 bug 当初被写出来的方式:调用方看到 multiTenant 就填了个布尔进去,完全合理,而结果是错的。
  2. 删除把「错误接线」从运行期挪到编译期。 tenancyPosture 的类型是 TenancyPosture,resolveMultiOrgEnabled() 返回 boolean —— 现在把 banner 接回旧 knob 无法通过编译,而不是产出一行看起来很合理的错话。这是本仓「在源头就不给出错的机会」的取向:结构上防止,而不是消费端兜底。
  3. 布尔在语义上本来就不够用。 tenancy 自 ADR-0105 起是三值谱系,布尔根本没有 group 的拼法 —— 一个 group 部署只可能被误报。压扁本身就是漂移藏身的地方。

影响面为零:format.ts 没有从 @objectstack/cli 的入口 re-export,ServerReadyOptions 是包内部接口;全仓 printServerReady 只有 serve.ts 一个调用点(已改齐,并顺手从 import 里去掉了因此不再使用的 resolveMultiOrgEnabled)。

测试:三种场景 + 反向验证

新增 packages/cli/src/utils/format.tenancy.test.ts。被断言的性质不是「这行看起来对」,而是 banner 与 resolveTenancyPosture() 不可能不一致:每个用例都从环境变量算出 resolveTenancyPosture(),再断言打印出的 token 就是它。

覆盖(前三条即议题点名的三种):

场景 OS_TENANCY_POSTURE OS_MULTI_ORG_ENABLED 期望
posture 显式非 single(cloud#1020 那条) isolated unset Tenancy: isolated
posture unset、布尔 true(回退路径) unset true Tenancy: isolated
两者矛盾 · posture 弱 single true Tenancy: single
两者矛盾 · posture 强 isolated false Tenancy: isolated
布尔表达不了的 posture group unset Tenancy: group
默认 unset unset Tenancy: single
legacy 拼法 multi unset Tenancy: isolated

外加:省略 posture 时整行不打印;任何 posture 下输出都不再出现 multi-tenant/single-tenant 措辞(先断言该行存在再断言它不含旧措辞 —— 一个没有 Tenancy: 行的 banner 会让 not.toMatch 空转通过,那和本文件要抓的 bug 从外部看一模一样);以及两条 @ts-expect-error,在 pnpm typecheck 而非 review 阶段钉住形状。

反向验证(把 format.ts stash 回改前,只留新测试与调用点)

$ git stash push -- packages/cli/src/utils/format.ts
$ pnpm --filter @objectstack/cli exec vitest run src/utils/format.tenancy.test.ts

  × posture explicitly isolated, boolean unset — the cloud#1020 lie 11ms
  × posture unset, boolean true — the legacy fallback still reads isolated 2ms
  × posture single while the boolean says true — posture wins 2ms
  × posture isolated while the boolean says false — posture wins 1ms
  × prints group — the posture a boolean can only misreport 1ms
  × posture unset and boolean unset — single, the default 1ms
  × accepts the legacy 'multi' spelling and prints its canonical name 1ms
  × prints the posture verbatim for every posture the spec defines 1ms
  × rejects the boolean shape at COMPILE time, not at review time 4ms

AssertionError: expected undefined to be 'Tenancy: single'
AssertionError: expected [ '  Tenancy: multi-tenant' ] to have a length of 2 but got 1

 Test Files  1 failed (1)
      Tests  9 failed | 2 passed (11)

(注:上面这轮跑的是加强前的版本,当时「never flattens」那条空转通过;已按上一段所述改成先断言行存在。)

改动在位后:

$ pnpm --filter @objectstack/cli exec vitest run src/utils/format.tenancy.test.ts
 Test Files  1 passed (1)
      Tests  11 passed (11)

两条 @ts-expect-error 也做了同样的反向验证 —— 去掉其中一条,tsc 立刻报出被它挡住的那个错,证明指令是承重的而非装饰:

packages/cli/src/utils/format.tenancy.test.ts(174,79): error TS2322:
  Type 'true' is not assignable to type '"group" | "single" | "isolated" | undefined'.

其它验证

  • ESLint:npx eslint packages/cli/src/utils/format.ts packages/cli/src/utils/format.tenancy.test.ts packages/cli/src/commands/serve.ts → 干净(无输出)。
  • Typecheck:本 worktree 里 pnpm --filter @objectstack/cli typecheck 会报一批 TS2307: Cannot find module '@objectstack/runtime' | '@objectstack/core' | …,这是工作区未构建 dist 的既有环境噪音(在未改动的 origin/main 树上同样复现),与本改动无关。改动涉及的两个文件已用一份把 @objectstack/spec / @objectstack/types 映射到源码的 tsconfig 单独 tsc --noEmit 过,结果零错误;CI 会先构建再 typecheck,是权威一轮。
  • 同理,src/utils/ 里 7 个测试文件在本地因同样的未构建依赖而 collect 失败,sqlite-occupancy.test.ts 有 1 条环境相关失败 —— 均已通过 stash 全部改动、在等同 origin/main 的树上复跑确认为既有失败(Tests 1 failed | 14 passed,一字不差)。

边界

只做 banner 这半块。未触碰 serve.ts 里的 tenancy 守卫逻辑(#4818 / PR #4858 刚改过),也未触碰 cloud#1020 那边仍在等维护者拍板的许可闸门决策 —— 正如议题开头所写,banner 无论那个决策怎么落都该修。已附 changeset(@objectstack/cli patch,CLI 输出为用户可见变化)。


Generated by Claude Code

…egacy boolean (#4801)

[ADR-0105 D1] `OS_TENANCY_POSTURE` is the authoritative tenancy knob and
`OS_MULTI_ORG_ENABLED` survives only as the fallback `resolveTenancyPosture()`
consults when the posture is unset. serve's runtime wiring already keys off the
posture, but the banner's `Tenancy:` row printed a boolean sourced from
`resolveMultiOrgEnabled()` — two sources for one fact. Booting with
`OS_TENANCY_POSTURE=isolated` alone printed `Tenancy: single-tenant` one line
above a plugin table listing `Organizations` (cloud#1020): the diagnostic
surface contradicted the runtime it describes.

- `ServerReadyOptions.multiTenant` (boolean) → `tenancyPosture: TenancyPosture`,
  printed verbatim (`single` / `group` / `isolated`). The boolean is removed
  rather than kept as a compat field: with the posture authoritative it could
  only be a field the printer ignores, and typing the replacement as
  `TenancyPosture` makes the old wiring a compile error instead of a
  plausible-looking wrong line. `group` was never expressible as a boolean.
- serve passes `resolveTenancyPosture()` — the same call the wiring uses — and
  no longer imports `resolveMultiOrgEnabled` at all.
- `format.tenancy.test.ts` pins the invariant "printed token IS
  `resolveTenancyPosture()`" across posture-set/boolean-unset, posture-unset/
  boolean-true, both-set-and-contradicting (both directions), legacy `multi`,
  and `group`; two `@ts-expect-error` directives pin the shape at typecheck.

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 7:04pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/m 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.

21 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)
  • content/docs/releases/v17.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 19:06
@xuyushun441-sys
xuyushun441-sys added this pull request to the merge queue Aug 3, 2026
Merged via the queue into main with commit b11a7e3 Aug 3, 2026
24 checks passed
@xuyushun441-sys
xuyushun441-sys deleted the claude/issue-4801-banner-tenancy-posture branch August 3, 2026 19:23
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