Skip to content

ADR-0105 D6 ② 只拦 business_unit 收件人,漏了 unit_and_subordinates —— 同一个跨 org BU 授权,更宽的那个反而放行 #4991

Description

@xuyushun441-sys

发现于 #4984 的实现过程(那一单只修被拒别名的读法,这条是覆盖面问题,按 Prime Directive #10 单独记账)。

事实

packages/lint/src/validate-org-axis-red-lines.ts 的 ORG_AXIS_CROSS_ORG_BU_GRANT(D6 ②)只对一种收件人类型判红:

const recipientType = str(sharedWith?.type);
if (recipientType !== 'business_unit') return;

而 ShareRecipientType 里有两个业务单元收件人:

  • business_unit —— 正好一个 BU 的成员(无子树);
  • unit_and_subordinates —— 一个 BU 加上其全部后代单元的成员(ADR-0057 D5 子树扩张)。

ADR-0105 D6 ② 的原文点名的恰恰是后者:

Every BU mechanism — unit_and_subordinates sharing, adminScope delegation, depth scopes — operates within one organization. There is no cross-org tree.

后果

在 tenancy.enabled: false(或 systemFields.tenant: false)的平台级对象上:

收件人 现状
sharedWith: { type: 'business_unit', value: 'bu_x' } 判红 ✅
sharedWith: { type: 'unit_and_subordinates', value: 'bu_x' } 放行 ❌

两者的缺陷完全相同 —— 平台级对象没有 organization 列可供 Layer 0 收口,授权跨到库里每一个 organization —— 而漏掉的那个授权面更大(整棵子树而非单个单元)。规则自己的文档注释也只写了 business_unit,所以这不像是有意的取舍,更像是照着一个收件人写完就没回头看枚举。

注:#4984 之前这条规则读的是被拒别名 sharedTo/recipient,所以 ② 对任何 spec 合法的 stack 都从不触发;#4984 修好键名后这个覆盖面缺口才第一次变得可观察。

建议

把判定改为收件人类型 ∈ { business_unit, unit_and_subordinates },并在 message 里保留具体类型名。这是 error 级门禁的扩张,所以没有随 #4984 一起做 —— 需要先确认没有真实元数据踩中。

已核查(#4984 分支上):examples/app-showcase、examples/app-crm 的全部 sharing rule 中只有一条 unit_and_subordinates(share_new_inquiries_with_field_ops → showcase_inquiry),而 showcase_inquiry 没有 tenancy.enabled: false / systemFields.tenant: false,仓库里也没有任何对象关掉 tenancy —— 因此按上述扩张改完,现有真实元数据不会产生新红。

相关:#4984、ADR-0105 D6、ADR-0057 D5。

Activity

  1. xuyushun441-sys commented on Aug 3, 2026

    @xuyushun441-sys
    CollaboratorAuthor

    认领(PM 分派)

    要点:D6 ② 现只拦 business_unit,漏了 ADR-0105 D6 ② 原文点名且授权面更大的 unit_and_subordinates(整棵 BU 子树)。#4984 的 dev 已预核过按此扩张现有真实元数据不产生新红。


    Generated by Claude Code

  2. xuyushun441-sys commented on Aug 3, 2026

    @xuyushun441-sys
    CollaboratorAuthor

    审核通过 — PR #5004 已取消草稿并挂上 auto-merge(SQUASH)

    词表核对做到了本单要求的深度,且放行清单不是猜的:以 ShareRecipientType 五成员枚举为准,拦二放三;放行的 user/team/position 逐一按 expandRecipient 的运行时展开路径复核 —— 恰好只有拦的两个走 BusinessUnitGraphService,其余各走各的扁平展开。差集在规则注释里表格化,没有留未说明的项。position 的斟酌(D6② 原文提的 depth scope 锚在 position 上,但那是 SCOPE 机制不是收件人展开)也写进注释,免得下一个人重走一遍。

    根因防复发是这单最重要的一笔:新增结构性断言"拦截半 ∪ 放行半 == 枚举全集"。本单本身就是"照着一个收件人写完没回头看枚举"的产物 —— 将来加第六个成员会在词表处判红,而不是无声落进没人裁决过的那一桶。这与 #4835 的词表教训同型,这次直接内建了防线。

    运行时契约里那个刻意不可授权的 queue 也记了账并指明将来的回归点。真实元数据是独立重跑而非沿用 #4984 的预核(全仓非测试 sharedWith 只有两个 examples 共 11 条,0 red)。顺带修掉 hint 里指向不存在收件人类型("permission-set audience")的过时措辞,范围正当。

    落点 3 文件全在 packages/lint,20 → 25 测试全绿。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions