fix(auth): organization/create 判权威的 OS_TENANCY_POSTURE,不再判被降级的 OS_MULTI_ORG_ENABLED (#5233) - #5267
Merged
Merged
Conversation
…moted OS_MULTI_ORG_ENABLED (#5233) ADR-0105 D1 made `OS_TENANCY_POSTURE` the canonical tenancy knob and demoted `OS_MULTI_ORG_ENABLED` to a back-compat INPUT of `resolveTenancyPosture()`. Two sites in AuthManager kept reading the demoted boolean directly, so a deployment configured the documented way — posture only, legacy boolean unset — mounted the entire organization wall and still answered `403 Creating additional organizations is disabled on this deployment.` - `organizationHooks.beforeCreateOrganization` now judges `postureEnforcesWall(resolveTenancyPosture())`, the same knob serve.ts's ADR-0093 D5 boot guard keys on. Intent unchanged (single-org still refuses); only the knob is corrected, so the gate reads the REQUESTED posture exactly as the old boolean did. - `/auth/config`'s `features.multiOrgEnabled` keeps preferring the `tenancy` service, but its no-service fallback now resolves the posture instead of the demoted boolean. `resolveMultiOrgEnabled()`'s doc comment — which still instructed both of those sites to call it, written before the demotion — now says the opposite. Its code semantics, and resolveTenancyPosture()'s fallback chain, are untouched. New `org-create-posture-gate.test.ts` drives the real better-auth pipeline (sign-up + POST /organization/create through AuthManager.handleRequest) across posture-only, legacy-only, group, single and degraded deployments, and asserts `/auth/config` predicts the route's answer in each. Ten of its cases fail against the pre-fix source. Fixes #5233 Refs cloud#1012, cloud#1020, #5261, #5262 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015W6nhsDrz6zWQc8je12a1t
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 10 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…ectQL delete dispatch The #5233 fake engine's delete() accepted call shapes ObjectQLEngine.delete refuses, so check:engine-double-contract flagged it as an unpinned double. Route it through assertEngineDeleteDispatch from '@objectstack/objectql' — the same in-package pattern as auth-manager.jwt-eddsa-fallback.test.ts and session-of-record.test.ts (#4550) — rather than taking a baseline entry. The devDependency was already present from #5044. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015W6nhsDrz6zWQc8je12a1t
os-zhuang
marked this pull request as ready for review
August 4, 2026 13:37
os-zhuang
enabled auto-merge
August 4, 2026 13:37
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 #5233
问题
ADR-0105 D1 把
OS_TENANCY_POSTURE立为权威 knob,OS_MULTI_ORG_ENABLED降级成resolveTenancyPosture()的回落输入。但AuthManager里有两个站点还在直接读那个被降级的布尔,于是一台按文档只设权威 knob 的部署(OS_TENANCY_POSTURE=isolated,遗留布尔不设)——组织墙整套挂上、tenancy 服务isolated、D5 不报警——POST /api/v1/auth/organization/create依然 403 "Creating additional organizations is disabled on this deployment."cloud#1012 的维护者决策(方案 B)是自助注册不自动开通组织,org-less 用户由 console 导到引导式「Create your workspace」。那条路径就是这个 403,所以平台对「新注册之后怎么办」的唯一答案是死路。与 cloud#1020 同形状,换了一个站点。
改法
两个站点,都在
packages/plugins/plugin-auth/src/auth-manager.ts:organizationHooks.beforeCreateOrganization—— 改判postureEnforcesWall(resolveTenancyPosture()),即serve.ts的 ADR-0093 D5 boot guard 用的同一个 knob。意图完全不变(单组织仍然拒绝),只是把 knob 从被降级的布尔换成权威的那个;判的仍然是「操作者请求的 posture」,与旧布尔同一个语义层级。判据抽成multiOrgPostureRequested(),per-request live 读、不缓存。/auth/config的features.multiOrgEnabled—— 仍然优先读tenancy服务(它才知道墙是否真的生效),但它没有服务时的 fallback 原本也是resolveMultiOrgEnabled() ? 'isolated' : 'single',同一份过期契约;现在回落到resolveTenancyPosture()。同时用 spec 自己的postureEnforcesWall()替掉手写的!== 'single'。packages/types/src/env.ts里resolveMultiOrgEnabled()那段「every site … MUST call this」的注释(写在降级之前,正是它把这两个站点带偏的)订正为反向指引:明确写「拿它回答『是不是多组织』就是 bug」,并指向postureEnforcesWall(resolveTenancyPosture())/tenancy服务。纯注释,代码语义零改动 ——resolveTenancyPosture()的 fallback 链原样保留(遗留布尔的 back-compat 全靠它)。packages/spec/**零改动。兼容性
不需要任何部署改配置:
OS_TENANCY_POSTURE=isolated(遗留布尔不设)OS_TENANCY_POSTURE=groupOS_MULTI_ORG_ENABLED=true(posture 不设)OS_TENANCY_POSTURE=isolated+OS_MULTI_ORG_ENABLED=true(此前的绕法)single/ 都不设cloud 控制面无影响:它的 worker 本来就显式设
OS_MULTI_ORG_ENABLED=true,两种读法同解。测试
新增
packages/plugins/plugin-auth/src/org-create-posture-gate.test.ts(19 例),走真实 better-auth 管线 —— 真实 sign-up 拿 cookie,再POST /organization/create经AuthManager.handleRequest,断言真实 HTTP status/body(遵循 #3585 / #4785 的先例:改真东西,别 stub 我们自己的代码)。只断言 hook 函数会重演原来的盲区:现场那个 403 是挂载的路由吐出来的,那就得由路由来回答。每个场景同时断言
/auth/config的features.multiOrgEnabled,末尾一张表把「flag 必须精确预测路由的答案」钉成不变量 —— #5233 之所以难看见,正是因为 flag 说 yes、路由说 no,而没有任何东西比对过这两者。对未修复源码跑,19 例中 10 例失败(
AssertionError: expected 403 to be 200),修复后全绿。下游证据:cloud PR #1091 在 cloud 侧落了一条 e2e,这个修复要把它从 403 翻成 200 —— 那是 cloud#1012 钉住的真实
objectstack serve+ 真实 HTTP 探针,本仓跑不了它;本 PR 的框架侧测试是照那个形状写的。两条刻意没有在本 PR 里做的事(已各自建 issue)
#5261 —— 闸门判「请求的」还是「实际生效的」posture? 降级态(ADR-0093 D5:请求了墙但企业包缺席)下两者分叉:闸门放行、
/auth/config藏按钮。这个分叉不是本 PR 引入的(旧布尔同样是纯 env 读,在这里同样返回 true),改它是一次实打实的能力收缩 —— 没装企业包的 OSS 部署将完全无法建组织,并且packages/qa/dogfood/test/org-create-default-team.dogfood.test.ts(#3624 的 e2e)靠「boot 后翻 env、闸门 live 读」开路,收紧之后无论如何都过不了、只能改写或删掉。那是维护者的决定,不是 knob 纠正,所以本 PR 把它作为当前行为钉在测试里(用例名带 "pinned as CURRENT behaviour (#5261)"),无论将来怎么定,都必须有意识地改那条断言。已验证 #3624 的 dogfood 在本 PR 下仍然通过。#5262 —— plugin-auth 之外还有 6 处同类误读。 issue 要求的普查做完了:
objectql/src/registry.ts(多租户系统字段注入)、plugin-dev(压根不加载@objectstack/organizations→ 直接掉进降级态)、runtime/src/app-plugin.ts两处(inline seed / 热重载 seeder)、driver-sql(tenant-audit 告警,还把结果缓存了)、cli/verify。本 PR 的文件面被限定在plugin-auth+types的一条注释,一处未改,全部归档在 #5262,建议一处一处改并各自带测试。🤖 Generated with Claude Code
https://claude.ai/code/session_015W6nhsDrz6zWQc8je12a1t
Generated by Claude Code