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.

@os-zhuang
os-zhuang marked this pull request as ready for review August 4, 2026 16:05
@os-zhuang
os-zhuang enabled auto-merge August 4, 2026 16:05
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 4, 2026
Merged via the queue into main with commit de113a4 Aug 4, 2026
25 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5261-effective-posture-gate branch August 4, 2026 16:24
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