Skip to content

fix(auth,verify)!: organization/create 判实际生效的 posture,降级部署不再产出无墙的组织 (#5261) - #5294

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-5261-effective-posture-gate
Aug 4, 2026
Merged

fix(auth,verify)!: organization/create 判实际生效的 posture,降级部署不再产出无墙的组织 (#5261)#5294
os-zhuang merged 1 commit into
mainfrom
claude/issue-5261-effective-posture-gate

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5261

维护者 2026-08-04 裁定:选 B(闸门改判「实际生效的 posture」),破坏性能力收缩,搭 v17 主版本车。

问题

#5233 修好之后,auth-manager.ts 的两个站点读的是两个不同的事实

站点 读什么
organizationHooks.beforeCreateOrganization postureEnforcesWall(resolveTenancyPosture()) —— 操作者请求的 posture
/auth/configfeatures.multiOrgEnabled tenancy?.posture ?? resolveTenancyPosture() —— 实际生效的 posture

只有一种形状让两者分叉,而那恰恰是最不该放行的一种 —— ADR-0093 D5 降级态:请求了 isolated/group,但企业包 @objectstack/organizations 缺席,tenancy.posture 解析为 singledegraded=true。此时闸门放行/auth/configmultiOrgEnabled=false 把入口藏起来:UI 不给按钮而 API 打得通,且建出来的每个组织都是没有任何引擎强制的租户边界 —— 声明了但没强制,ADR-0049 最讨厌的那一类,发生在部署层。

一条独立佐证:packages/specPUBLIC_AUTH_FEATURES 早就声明 multiOrgEnabled 把守 sys_organization.actions.create_organization,并写明它 "Reflects ACTUAL multi-tenancy capability … not just the requested posture"。出线的一直是路由,不是 flag —— 本 PR 让路由回到它自己的声明契约上。

改法(方案 B)

packages/plugins/plugin-auth/src/auth-manager.ts,判据抽成一处、两个站点共用同一次求值:

private effectiveTenancyPosture(): TenancyPosture {
  return this.config.getTenancy?.()?.posture ?? resolveTenancyPosture();
}
private multiOrgPostureEffective(): boolean {
  return postureEnforcesWall(this.effectiveTenancyPosture());
}

tenancy 服务权威,是因为只有它知道墙是否真的立着(它 probe 企业 org-scoping 运行时,把立不住的请求降解为 single + degraded);只有从未注册该服务的精简嵌入才回落到 env 解析。getPublicConfig() 现在调用的是同一个方法 —— 两站点不再是「两处写法碰巧一致」,而是一次求值

仍是 per-request live 读、不缓存:org-scoping 在 plugin-auth 之后注册,墙确实是 boot 中途立起来的,冻结任何一层都会把部署钉死在立墙前的状态(AGENTS.md 的 startup-registry 规则)。

两笔代价的处置

代价一(有意为之):没装企业包的部署将完全无法创建组织,任何 env 组合都不行。

部署形状 改前 改后
有企业包,isolated / group(墙真立着) 200 200(不变)
请求了墙但企业包缺席(D5 降级态) 200 403 ⚠️
single / 两个 knob 都不设 403 403(不变)
未注册 tenancy 服务的精简嵌入 按 env 按 env(不变)

serve.ts 本就在降级态默认拒绝启动(要 OS_ALLOW_DEGRADED_TENANCY=1),所以这条收缩命中的是一个已经需要显式选择才能到达的形状:从此那里的 org-create 路由也一并拒绝,而不是半通不通。cloud 控制面与任何装了企业包的部署不受影响。

代价二(未删测试)packages/qa/dogfood/test/org-create-default-team.dogfood.test.ts#3624 回归)靠「boot 后翻 env、闸门 live 读」开路,本次收缩把这个绕法关死了。按 issue 自己的建议给它另一条开路方式

@objectstack/verifyBootOptions.multiTenant 新增取值 'posture-only' —— 注册一个内置的 org-scoping 服务替身(SimulatedOrgScopingPlugin),挂在企业插件自己的槽位上,于是 tenancy 服务解析出真实、非降级isolated posture,整个栈口径一致。

这比原来的 env flip 更诚实:原来是单租户栈只对一个闸门撒谎(BYPASS),现在模拟的是真实的有墙部署(DEPLOYMENT)。dogfood 因此新增一条「守卫的守卫」用例,直接断言 tenancy.posture === 'isolated'degraded === false/auth/configmultiOrgEnabled === true —— 若替身哪天失效,报出来的会是「前置条件塌了」,而不是把人引去查一个并不存在的 team-insert bug。

⛔ 该选项明确声明不做任何租户隔离(不 stamp organization_id、不 scope 查询):它让部署的 posture 为真,不是让为真。文档里写死了这条边界,并点名「用它断言跨租户隔离 = #4700 那个 constant-false 能力探针反过来戴一次面具」。跨租户隔离的唯一诚实证明仍是 multiTenant: true + 真实企业包,那些 gate 在本仓继续 skip 而不是假装通过

新增 packages/verify/src/harness.posture-only.test.ts 覆盖这个新公共 API,刻意不 mock @objectstack/organizations(与 harness.posture.test.ts 相反)—— 待证命题正是「它根本不需要企业包」,旁边摆一个 mock 会让该命题不可证伪。

那颗自相矛盾的钉子

org-create-posture-gate.test.ts 里带 pinned as CURRENT behaviour (#5261) 的用例是 #5267 故意钉住旧行为的,本次就是那个「将来」:断言、用例名、注释、文件头一起翻,没有留下说着「这是当前行为」却断言着新行为的钉子(全仓 grep pinned as CURRENT behaviour 只剩本 PR 里那句叙述历史的散文)。

不变量表(#5267 建立的「flag 必须精确预测路由的答案」)现在覆盖每一种形状、不再挖例外:此前它显式排除降级行,因为那里两站点按设计答不同的事实;例外的消失本身就是这次改动的证明。新增三行降级用例(isolated 降级、group 降级、两个 env knob 一起喊 yes 的降级)。

测试沿用 #5267 的形状:真实 better-auth 管线(真实 sign-up 拿 cookie → POST /organization/createAuthManager.handleRequest),断言真实 HTTP status/body;每个场景同时断言 /auth/config。另加一条「墙在 plugin-auth 之后才立起来」的 live-probe 用例。

对抗性验证(真实执行)

1. 对未修复源码跑(只把闸门判据回退成 postureEnforcesWall(resolveTenancyPosture())):

 FAIL  src/org-create-posture-gate.test.ts > DEGRADED (isolated requested, no enterprise runtime): the gate REFUSES — #5261
 AssertionError: expected 200 to be 403
 FAIL  … > sees the enterprise runtime that registers AFTER plugin-auth — the probe is live too
 FAIL  … > 'tenancy service: isolated requested +…' / 'group requested + DE…' / 'DEGRADED with both e…'

 Test Files  1 failed (1)
      Tests  7 failed | 18 passed (25)

修复恢复后:Test Files 1 passed (1) / Tests 25 passed (25)

2. 把 dogfood 换回旧的 env-flip 绕法(证明新开路方式是承重的、代价二真实存在):

 × boots a genuinely walled posture …   AssertionError: expected 'single' to be 'isolated'
 × creates the org AND its default team without a 500
   AssertionError: organization/create returned 403:
   {"message":"Creating additional organizations is disabled on this deployment."}
 Test Files  1 failed (1) / Tests  2 failed (2)

测试

pnpm --filter @objectstack/plugin-auth test   → 34 files / 750 tests passed
pnpm --filter @objectstack/verify test        → 4 files / 17 tests passed
@objectstack/dogfood(全量)                   → 85 passed | 1 skipped (86 files) / 506 passed | 3 skipped
turbo typecheck --filter=plugin-auth --filter=verify --filter=dogfood → 63/63 OK
eslint --no-inline-config(改动文件)          → exit 0
check:engine-double-contract                  → OK(22 pinned / 31 DEBT / 1 exempt)
check:adr-anchors / check:doc-authoring / check:org-identifier /
check:role-word / check:startup-registry-verdict → 全部 OK

changeset:@objectstack/plugin-auth major(能力收缩讲清楚 + 迁移指引)、@objectstack/verify minor。⛔ 未触碰 content/docs/releases/

证据边界

🤖 Generated with Claude Code

https://claude.ai/code/session_015W6nhsDrz6zWQc8je12a1t


Generated by Claude Code

…5261)

`beforeCreateOrganization` 此前判操作者**请求的** posture
(`postureEnforcesWall(resolveTenancyPosture())`),与 `/auth/config` 的
`features.multiOrgEnabled`(判**生效** posture)在 ADR-0093 D5 降级态下分叉:
console 藏起「创建组织」按钮,API 却照建,且建出来的每个组织都是没有任何引擎
强制的租户边界 —— 声明了但没强制,ADR-0049 最讨厌的那一类。

闸门改判 `tenancy?.posture ?? resolveTenancyPosture()`,与 `/auth/config`
共用同一次求值(`effectiveTenancyPosture()`),两站点从此同解、永不分叉。
`packages/spec` 的 `PUBLIC_AUTH_FEATURES` 本就声明 `multiOrgEnabled` 把守
`sys_organization.actions.create_organization` 且「reflects ACTUAL … not just
the requested posture」——出线的一直是路由。

BREAKING:没有安装企业包 `@objectstack/organizations` 的部署将完全无法创建组织,
任何 env 组合都不行。有意为之,搭 v17 主版本车;`serve.ts` 本就在降级态默认拒绝
启动(要 `OS_ALLOW_DEGRADED_TENANCY=1`)。

#3624 的 dogfood 此前靠「boot 后翻 env、闸门 live 读」开路,本次收缩把这个绕法
关死。**未删测试**:`@objectstack/verify` 的 `BootOptions.multiTenant` 新增
`'posture-only'`,注册内置的 `org-scoping` 服务替身,让 tenancy 服务解析出真实、
非降级的 `isolated` posture —— 模拟真实的有墙部署,而不是让闸门放行。该选项
明确声明不做任何租户隔离,跨租户证明仍只认 `multiTenant: true` + 真实企业包。

对抗性验证:对未修复源码跑 org-create-posture-gate.test.ts,25 例中 7 例红
(`expected 200 to be 403`);把 dogfood 换回旧的 env-flip 绕法,得到
403 "Creating additional organizations is disabled on this deployment."

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015W6nhsDrz6zWQc8je12a1t
@vercel

vercel Bot commented Aug 4, 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 4, 2026 4:02pm

Request Review

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

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/plugin-auth, @objectstack/verify.

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

  • content/docs/deployment/cli.mdx (via @objectstack/plugin-auth)
  • content/docs/deployment/production-readiness.mdx (via @objectstack/plugin-auth)
  • content/docs/kernel/contracts/cache-service.mdx (via @objectstack/plugin-auth)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/plugin-auth)
  • content/docs/permissions/authentication.mdx (via @objectstack/plugin-auth)
  • content/docs/permissions/sso.mdx (via @objectstack/plugin-auth)
  • content/docs/plugins/index.mdx (via @objectstack/plugin-auth)
  • content/docs/plugins/packages.mdx (via @objectstack/plugin-auth)
  • content/docs/releases/implementation-status.mdx (via @objectstack/plugin-auth, @objectstack/verify)
  • content/docs/releases/v15.mdx (via @objectstack/verify)
  • content/docs/releases/v9.mdx (via @objectstack/plugin-auth)

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 free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

2 participants