You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Does this row EXIST? A fact about the database — deliberately NOT "may this caller see it".
…probing with the CALLER's context turns the existence gate into an authorization gate
单记录 updateData 因此用系统上下文探针;upsert 这里是同一个区分朝反方向踩空。
影响
一个受行级作用域限制的调用者 upsert 一个确实存在、但在他作用域之外的 id 时,会进入 insert 分支:
读 #5088(by-id 批量写补存在性闸门)时在同一个循环里读到的相邻问题,未在本次 PR 范围内修改(#5088 的验收面只含 update / delete 两个分支)。
现状
packages/metadata-protocol/src/protocol.ts,runBatchDataLoop的upsert分支,用调用者的执行上下文去问「这条记录在不在」:ctxOpt就是 REST 透传下来的 caller context(#3455 特意接上的),所以 RLS / sharing 会收窄这次读。也就是说:exists-or-insert 这个分岔是按「可见性」判的,不是按「存在性」判的。同文件
probeRecord的注释把这条区分写成了承重点(#4435):单记录
updateData因此用系统上下文探针;upsert这里是同一个区分朝反方向踩空。影响
一个受行级作用域限制的调用者 upsert 一个确实存在、但在他作用域之外的 id 时,会进入 insert 分支:
顺带一提,即使不谈 RLS,这里也和 #5088 修完之后的 update / delete 分支口径不一致了:那两个分支现在问存在性,upsert 仍问可见性。#4620 对本文件的要求是三个 bulk 写面共用一份口径。
需要拍板的地方(所以没有顺手改)
upsert 的正确语义不是显然的,有两种读法,后果不同:
probeRecord(存在性):作用域外的既有行走 update 分支,写不写得进去交回engine.update里 fix(security)[P0]: enforce RLS on by-id writes — close member-edits-others'-records hole (#1985) #1994 的 by-id 前像检查决定 —— 与 data: PATCH/DELETE of a nonexistent record answer 200 success instead of RECORD_NOT_FOUND #4435 / data: updateMany runs hooks for a nonexistent id — the row fails INTERNAL_ERROR from a hook-condition abort instead of RECORD_NOT_FOUND #5088 的取向一致,授权判断留在写策略那一层。倾向 1(和已确立的「探针只问存在性、授权归写策略」一致),但这会改变一个已发布端点在 RLS 部署下的可观察行为,按契约优先的规矩应由维护者定,不由实现方顺手决定。
复现状态
据源码判读,未做端到端复现(需要一套带行级策略的 fixture + upsert 批量请求)。分类与优先级请按 triage 轮次判定 —— 这里只做如实记录。
发现于 #5088 的实现过程。
https://claude.ai/code/session_01BotUP49pqhvqGY393n2HfU