Repository navigation
单库多租户没有端到端验证 —— 且它无法在本仓完成:组织运行时是 cloud 私有包,本仓只能证明墙以下的部分 #1161
Description
核实于 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)在重放器。它们全部只有在真启动、真建两个组织、真跑一遍种子的场景下才会现形。
关联
- 姊妹 issue:DEPLOYMENT.md 没有单库多租户的部署说明 —— 全仓
OS_TENANCY_POSTURE零命中,而漏配其中任何一项都会静默降级或拒绝启动 #1160(DEPLOYMENT.md 缺多租户运维说明) - 已修复的多租户缺陷:Ownership model mismatch: app-authored
ownerlookups vs the platform'sowner_id— reassigning Owner in the UI does not move record access #548、crm_account.name uniqueness is platform-wide, not per-tenant — two orgs cannot both have an "Acme Corp" #625 / crm_case 的 [organization_id, case_number] 手写复合唯一索引在无 organization 的装机上什么也不约束,且 Protocol 18 拒收该拼法(ADR-0120) #1023、[17.0-rc][疑似平台] crm_case 自动编号计数器落后于既有 case_number:REST 建单连续 409,每次失败还烧掉一个号(实测重试 25 次才成功) #698、[17.0-rc][疑似平台] 自动化引擎 create_record(系统扫)插入的行 owner_id/organization_id/created_by 全 NULL —— 出生即连 admin 都无法改删;demo_bootstrap 十分钟内会"自愈" owner_id,真实安装没有这根拐杖 #700(上游 objectstack#5494,修复已在 17.0.0) - 参考实现:cloud
apps/ee-tenant-crm-showcase/test/isolated-posture.dogfood.test.ts、cloud#994 / cloud#1009
Activity
os-zhuang commentedon Aug 16, 2026
方向已定:验收放 cloud 仓,按 SHA pin 住 hotcrm。cloud 侧已立 objectstack-ai/cloud#1350(
pm:queue)。维护者确认多租户能力本身就是收费产品,所以
@objectstack/organizations继续闭源,本仓拿不到组织运行时这条约束是永久的、不是待解决的问题。更正本 issue 正文的一处判断
正文写「本仓做不了完整的那半」——结论对,但当时给的理由不完整,我在没看到既有模式之前就下了判断。
事实上平台为这种情况设计过一套模式:
packages/qa/dogfood/test/enterprise-organizations.ts(objectstack 仓)——可选加载企业包 + 可证伪的 skip:配
OS_TEST_MULTI_ORG_ENABLED=1:声明「这次运行应该有企业包」,缺了就响亮地红而不是静默跳过("Absence must be loud")。所以技术上本仓是可以承载这套测试的——公开 CI 具名 skip,持包环境真跑。但维护者选择了 cloud 路线,这个选择更好,理由不是技术可行性:
objectos-ee和MarketplaceInstallLocalPlugin;本 issue 剩下的范围
cloud#1350 覆盖了验收本身。本 issue 保留两件本仓该做的事:
OS_TENANCY_POSTURE零命中,而漏配其中任何一项都会静默降级或拒绝启动 #1160 合并做):在docs/DEPLOYMENT.md写明多租户验收在 cloud 仓何处、改动对象或种子后需要去那边重跑并升 pin。没有这条,这个依赖只存在于某个人的记忆里——cloud 侧 pin 住 hotcrm 解决了「cloud 不跟着漂」,解决不了「hotcrm 改了没人知道」。single/degraded状态,并在文件头写明「这不是组织墙的验证」。否则它会变成一张假的验收凭据——已发布的@objectstack/verify在orgContext文档块里把这个陷阱称作「the precise trap this option's NAME is chosen to stay clear of」。Generated by Claude Code
os-zhuang commentedon Aug 16, 2026
作废 —— 维护者定:只保留一条测试路径,即 objectstack-ai/cloud#1350。
本 issue 原本留了两件事,两件都撤销:
test/account-name-tenant-scope.test.ts/test/case-number-tenant-scope.test.ts已经用真实 SQLite 覆盖了这一层。再加一层只会制造第二条需要维护、且永远跑在single/degraded状态的验证路径——两条路径就是两处会各自陈旧的地方。.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