背景
#3004 修复(PR #3018)后,plugin-security step 3.5 把 owner_id 变成非特权写入者的系统托管字段:update 携带 owner_id(含置为 null 的弃权)在无 transfer 授权时被拒。
问题
owner_id 是指向 sys_user 的 lookup,默认 deleteBehavior: 'set_null'(packages/objectql/src/registry.ts 约 334–345)。删除一个 sys_user 时,cascadeDeleteRelations(packages/objectql/src/engine.ts 约 2527+,仅对单条删除生效)会对每个引用该用户的子记录执行:
this.update(childName, { id: depId, owner_id: null }, { context }) // 沿用删除者的上下文
该级联 update 携带 owner_id: null,以删除者的上下文重新进入中间件。若删除者是非 isSystem、且在这些子对象上不持有 allowTransfer/modifyAllRecords 的主体,step 3.5 会把它判为“弃权”而抛 PermissionDeniedError —— 由于没有事务包裹,级联会中途失败,留下部分已置空/已删除的子记录。
触发条件(较窄)
- 仅在删除
sys_user(唯一被 owner_id 引用的对象)时触发;
- 且删除者是非系统上下文、缺少 transfer 授权(平台/组织管理员默认持有
modifyAllRecords,不受影响)。
期望
级联 set_null 是引擎内部的引用完整性操作,不是用户发起的属主变更,不应被属主转移守卫拦截。可选方案:
cascadeDeleteRelations 的 set_null 写入使用受控的系统上下文(引用完整性本就应覆盖删除者不可见的子记录);或
- 为引擎内部级联写入打标,step 3.5 对其豁免。
关联
背景
#3004 修复(PR #3018)后,
plugin-securitystep 3.5 把owner_id变成非特权写入者的系统托管字段:update 携带owner_id(含置为null的弃权)在无 transfer 授权时被拒。问题
owner_id是指向sys_user的lookup,默认deleteBehavior: 'set_null'(packages/objectql/src/registry.ts约 334–345)。删除一个sys_user时,cascadeDeleteRelations(packages/objectql/src/engine.ts约 2527+,仅对单条删除生效)会对每个引用该用户的子记录执行:该级联 update 携带
owner_id: null,以删除者的上下文重新进入中间件。若删除者是非isSystem、且在这些子对象上不持有allowTransfer/modifyAllRecords的主体,step 3.5 会把它判为“弃权”而抛PermissionDeniedError—— 由于没有事务包裹,级联会中途失败,留下部分已置空/已删除的子记录。触发条件(较窄)
sys_user(唯一被owner_id引用的对象)时触发;modifyAllRecords,不受影响)。期望
级联
set_null是引擎内部的引用完整性操作,不是用户发起的属主变更,不应被属主转移守卫拦截。可选方案:cascadeDeleteRelations的set_null写入使用受控的系统上下文(引用完整性本就应覆盖删除者不可见的子记录);或关联