fix(approvals): resolve department approvers against env-wide business units (#3807) - #3811
Merged
Merged
Conversation
…ess units (#3807) `expandBusinessUnitUsers` scoped its `sys_business_unit` reads with a strict `organization_id = <request org>` equality, so a unit whose `organization_id` is `null` was invisible: the seed check found no row, the expansion returned `[]`, and the approver fell back to the dead `department:<id>` literal that routes to nobody. That is the normal case, not an edge case. An app's org tree is seeded, and a seed cannot know the organization id the runtime mints at boot, so every seeded unit carries `organization_id = null` — while an approval request always carries one. Every business unit a flow author could pick therefore resolved to nobody, silently, and with `lockRecord` the record stays locked with no one able to act (#3424 is the downstream shape of the same dead end). Both the seed check and the subtree descent now scope to this org ∪ env-wide — `$or: [{ organization_id: <org> }, { organization_id: null }]` — the same predicate `sys_metadata`'s pending-draft listing settled on for the identical reason. The wall between two organizations is unchanged: another org's unit still fails the match, and a null-org parent does not drag another org's child unit into the subtree. Verified on a live showcase stack, same DB and same flow across builds: seeded (null-org) unit + old build → `pending_approvers: department:bu_hq_finance`; same unit + this build → the unit's member. Tests: 4 new cases (env-wide unit resolves; another org's unit stays invisible; a null-org parent does not pull in another org's child; an inactive env-wide unit still contributes nobody). The two positive cases fail without the source change. plugin-approvals 252/252 pass; repo lint clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BVRVgSvmyCwmDfTmKmCZoH
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 4 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
July 28, 2026 06:57
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.
Closes #3807.
问题
expandBusinessUnitUsers(approval-service.ts)读sys_business_unit时用的是严格等值organization_id = <请求的 org>,于是organization_id = null的单元完全不可见:种子检查查不到行 → 整个展开返回[]→ 审批人落到死值department:<id>,路由到没有人。这不是边角情况,而是常态。应用的组织架构一般是 seed 出来的,而 seed 阶段拿不到运行时才创建的组织 id,所以每一行都是
organization_id = null;审批请求却总是带着 org。结果:设计器 Department 选择器里能选到的每一个单元,都解析到没有人,而且是静默的 —— 请求照常打开、名单为空,配上lockRecord记录就此锁死没人能动(#3424 就是同一条死路的下游形态)。配合 #3508 刚落地的记录 lookup,这条路更容易被走到:以前要手敲 BU id,现在从选择器里点一下就得到一个死槽位。
改动
种子检查与子树下钻都改为「本组织 ∪ 无组织」:
与
sys_metadata待发布草稿列表为同一个原因(严格等值静默丢掉 env-wide 行)所采用的谓词一致。两个组织之间的墙没有变:别的组织的单元仍然匹配不上;
nullorg 的父单元也不会把别的组织的子单元拖进子树。只有「不属于任何组织」的行变得可见 —— 而这类行只可能由部署方(seed / 文件层 / bootstrap)写入,租户管理员写不出来(写路径会盖 org 戳)。验证
真机,同一个库、同一条 flow、只换构建:
pm8MCA…null(seed 原样)department:bu_hq_finance❌ 死值0h-cVa…usr_showcase_phone_demo✅(印证因果)p-G0OQ…nullusr_showcase_phone_demo✅单测:新增 4 例 —— env-wide 单元能解析;别的组织的单元仍然不可见;
nullorg 的父单元不会拉进别组织的子单元;停用的 env-wide 单元仍然不出人。其中两个正向用例在不改源码时确实失败(已回退验证)。plugin-approvals全量 252/252 通过;仓库pnpm lint干净。未一并改动的地方
plugin-sharing的BusinessUnitGraphService.orgScope有同形的严格等值。目前不可达 —— 实测所有物化出来的sys_sharing_rule行organization_id都是null,过滤器整个被跳过,BU 子树共享规则照常生效。它是一条授权路径(决定谁能看到记录),在没有可复现失败的前提下不宜跟着放宽,故本 PR 不动它,只在此备案。🤖 Generated with Claude Code
https://claude.ai/code/session_01BVRVgSvmyCwmDfTmKmCZoH
Generated by Claude Code