Skip to content

单库多租户没有端到端验证 —— 且它无法在本仓完成:组织运行时是 cloud 私有包,本仓只能证明墙以下的部分 #1161

Closed as not planned

Description

@os-zhuang

核实于 main @ d4ddee09(平台 @objectstack/* 17.0.0 GA)。

现状:证明只到「索引物化」那一层

test/account-name-tenant-scope.test.ts / test/case-number-tenant-scope.test.ts 用真实 SQLite(SqliteWasmDriver + initObjects)钉住了唯一索引按组织作用域、两个组织可以各有一个 Acme Corp。这一层是扎实的。

但全仓 grep -rn "OS_TENANCY_POSTURE" 零命中,没有任何测试:

  • 在 walled posture 下启动运行时;
  • 建两个 sys_organization 并让种子逐租户重放;
  • 断言一个组织的成员读不到另一个组织的行(ADR-0095 Layer 0);
  • 断言种子里的跨对象引用解析在重放的那个组织内(这条是读侧墙测试做不到的)。

关键约束:本仓做不了完整的那半

$ npm view @objectstack/organizations version
npm error 404 Not Found

@objectstack/organizations 是 cloud 私有包,未发布。而 cloud 的参考实现 apps/ee-tenant-crm-showcase/test/isolated-posture.dogfood.test.ts 正是 import { OrganizationsPlugin } from '@objectstack/organizations'。

该测试的场景 3 说明了没有它会发生什么:

RUNTIME ABSENT(铁律:强制免费、治理收费)—— the same posture request without the runtime resolves to single + degraded, never a silent unwalled multi-org.

即:hotcrm 用已发布依赖,压根没有两个组织可言——不是「墙测不出来」,是组织本身不存在。任何在本仓写的"多租户端到端测试"都只会在 single + degraded 状态下跑,然后绿给人一种虚假的安全感。这比没有测试更糟。

(@objectstack/verify 本身已发布,17.0.0,所以 harness 不是障碍;障碍是组织运行时。)

建议的拆法

① 本仓(可做,范围诚实):把现有两个 tenant-scope 测试补齐到「墙以下」的完整面——经 @objectstack/verify 的 bootStack 真启动,用两个不同 organization_id 写入,断言 ObjectQL 层的行隔离与种子重放的引用解析。必须显式断言自己跑在什么状态下(single/degraded),并在文件头写明「这不是组织墙的验证,墙的验证在 cloud」——否则下一个人会把它当成多租户已验证的凭据。

② cloud 仓(真正的验收,需跨分片移交):HotCRM 是 marketplace app,其设计部署路径就是装进 objectos-ee。所以完整验收天然属于 cloud——那里有企业运行时、有 ee-tenant-crm-showcase 现成的四场景形状(ENTITLED / FLAG DECOUPLING / RUNTIME ABSENT / UNLICENSED)。showcase 是为证明这件事而造的迷你 CRM;把 HotCRM 的真实 stack 喂进同一形状是它的自然延伸,也是唯一能回答「HotCRM 在单库多租户下真的可用吗」的做法。

按 pm-dispatch 的 cross-shard 协议,第 ② 项应在 cloud 仓另立 issue(带 pm:queue + Part of objectstack-ai/hotcrm#<本issue>),由 cloud 分片 PM 消化——不由本仓派发。

为什么值得做而不是「反正数据层测过了」

四条曾经真实的多租户缺陷都不是在索引层暴露的:#548(ownership 模型脱节)在权限层、#698(自动编号计数器 + 种子行 org 为 NULL)在启动与写入路径、#700 / objectstack#5494(系统上下文插入丢租户列)在自动化引擎、cloud#994(种子重放丢 organization_id)在重放器。它们全部只有在真启动、真建两个组织、真跑一遍种子的场景下才会现形。

关联

Activity

  1. os-zhuang commented on Aug 16, 2026

    @os-zhuang
    ContributorAuthor

    方向已定:验收放 cloud 仓,按 SHA pin 住 hotcrm。cloud 侧已立 objectstack-ai/cloud#1350(pm:queue)。

    维护者确认多租户能力本身就是收费产品,所以 @objectstack/organizations 继续闭源,本仓拿不到组织运行时这条约束是永久的、不是待解决的问题。

    更正本 issue 正文的一处判断

    正文写「本仓做不了完整的那半」——结论对,但当时给的理由不完整,我在没看到既有模式之前就下了判断。

    事实上平台为这种情况设计过一套模式:packages/qa/dogfood/test/enterprise-organizations.ts(objectstack 仓)——可选加载企业包 + 可证伪的 skip:

    const organizationsAvailable = await createHostImporter()('@objectstack/organizations')
      .then(() => true).catch(() => false);
    describe.skipIf(!organizationsAvailable)(...)

    配 OS_TEST_MULTI_ORG_ENABLED=1:声明「这次运行应该有企业包」,缺了就响亮地红而不是静默跳过("Absence must be loud")。所以技术上本仓是可以承载这套测试的——公开 CI 具名 skip,持包环境真跑。

    但维护者选择了 cloud 路线,这个选择更好,理由不是技术可行性:

    • 被测的是真实 marketplace app 在真实安装路径上的行为,而 cloud 才有 objectos-ee 和 MarketplaceInstallLocalPlugin;
    • 本仓路线要么把私有包发到私有 registry、要么依赖同级 checkout,两者都在开源仓里引入一条外部贡献者跑不了的依赖;
    • 而 HotCRM 的设计部署路径本来就是装进 EE——测试应当长在它真正运行的地方。

    本 issue 剩下的范围

    cloud#1350 覆盖了验收本身。本 issue 保留两件本仓该做的事:

    1. 反向指针(与 DEPLOYMENT.md 没有单库多租户的部署说明 —— 全仓 OS_TENANCY_POSTURE 零命中,而漏配其中任何一项都会静默降级或拒绝启动 #1160 合并做):在 docs/DEPLOYMENT.md 写明多租户验收在 cloud 仓何处、改动对象或种子后需要去那边重跑并升 pin。没有这条,这个依赖只存在于某个人的记忆里——cloud 侧 pin 住 hotcrm 解决了「cloud 不跟着漂」,解决不了「hotcrm 改了没人知道」。
    2. 墙以下的本仓自测(可选,成本低):现有两个 tenant-scope 测试已覆盖索引物化;若要再往前,必须显式断言自己跑在 single/degraded 状态,并在文件头写明「这不是组织墙的验证」。否则它会变成一张假的验收凭据——已发布的 @objectstack/verify 在 orgContext 文档块里把这个陷阱称作「the precise trap this option's NAME is chosen to stay clear of」。

    Generated by Claude Code

  2. os-zhuang commented on Aug 16, 2026

    @os-zhuang
    ContributorAuthor

    作废 —— 维护者定:只保留一条测试路径,即 objectstack-ai/cloud#1350。

    本 issue 原本留了两件事,两件都撤销:

    1. 墙以下的本仓自测 —— 撤销。它测的是索引物化与行级作用域,而现有的 test/account-name-tenant-scope.test.ts / test/case-number-tenant-scope.test.ts 已经用真实 SQLite 覆盖了这一层。再加一层只会制造第二条需要维护、且永远跑在 single/degraded 状态的验证路径——两条路径就是两处会各自陈旧的地方。
    2. 反向指针(提醒去 cloud 重跑) —— 撤销。我原来把它当成必须补的缺口,但那个判断没有对照本仓既有的运作方式:cloud 已经用同样的方式 pin 住 objectstack 和 objectui(.objectstack-sha / .objectui-sha),「谁记得升 pin」这个问题在那两个仓上同样存在,并且是已被接受的运作模型,不是本次新增的风险。给 hotcrm 单独立一条反向指针,等于为一个既有且被接受的模式发明一份例外文档。

    多租户运维说明已移交 cloud#1351(见 #1160 的关闭说明)。

    本仓的多租户结论(截至 main @ d4ddee09,平台 17.0.0 GA)

    四条曾经真实的缺陷全部关闭:#548(ownership 模型)、#625 / #1023(唯一索引按组织作用域,有真实 SQLite 回归测试)、#698(自动编号 + 种子行租户列)、#700(系统上下文插入丢租户列——上游 objectstack#5494 已修,且已在 17.0.0 的发布产物里;该 issue 本身仍 open,待其所有者复验后关闭,本会话未代动)。

    所以本仓没有已知的多租户缺陷,只是缺一次在真实企业运行时上的端到端证明——那件事在 cloud#1350。

    未产生任何代码、分支或 PR。


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions