Skip to content

[Decision] #20822 F7: retiring formula's whole-day copy — the RLS write check judges the raw post-image, so deleting the copy refuses writes the same policy's read shows #21109

Description

@objectstack-fleet

Ruled: 5933322270 · letter A · 2026-10-01T14:17Z

Filed by the domain:engine execution seat 2 (seat post #20966, session_01Ujdtvqs7ree7WyQmEDwEnG, os-litant) from #20822 group 3a's measured stop (os-dev-report 5926452254, open_questions[0]). This card holds only F7's retirement. #20822 group 3a (F6, PR #21105) lands without it, and group 3b carries F8. ⛔ Not a claim.

Escalated, not decided in-seat, because the options move a security boundary (what the RLS write check admits), which the PM protocol keeps on the human floor. One option also amends a Tier H ADR.

一句话问题

删掉 formula 里那份手抄的“整日上界”规则(F7)后,一个成员按 RLS 写策略插入一条日期型记录,会被拒(403);而同一条策略下,读出来这条记录又是可见的。写和读对同一条数据给出了相反的答案。

背景

  • 裁决 5902355785(batch 🔗 Broken links detected in documentation #248,D4 (b)):"drivers receive the seams' lowered input; no permanent local guards on faces that bypass a seam"。
  • ADR-0053 D-D1(amended)第 5 条:「No face keeps a permanent copy」;第 9 条:每份副本都有删除卡,且「A copy is idempotent on lowered input」。
  • 第 7 条把 RLS 留成了开放项:「The RLS compile seam reads nothing but the filter today, and its using output reaches typed drivers while its check output reaches matchesFilterCondition; which reading it applies is measured on the card that wires it.」本卡就是这次测量的结果。

测量(dev 在 #20822 第 3a 组,经 ObjectQL + SecurityPlugin + SqlDriver 实测;PR #21105 正文里有探针表):

  • RLS 写检查判的是原始写入镜像(raw post-image),不是落库后的形态。typed 的 RLS 接缝对 date 列不做下沉(item 7)。
  • 删掉 F7 的 lteBound 后:成员在边界当天写入 due_on,无论写成 ISO instant 还是 Date,都被拒 PERMISSION_DENIED/403。同一策略下,该成员的读能看到这行(存储为 2026-01-05)。
  • 保留 lteBound 时,两种写法都放行。
  • 第二处没有接缝下沉的路径:$lte 对一个解析成纯日期的 { $field, addDays } 引用。lowering 对 $field 比较值原样保留。
  • 结论:第 9 条的前提「副本对已下沉输入幂等」在这个面上不成立,所以 F7 按卡片的停止规则停下,没有删。

Governing text: ADR-0053 D-D1(amended)第 5、7、9 条,原文见上;裁决 5902355785 D4 (b)。没有任何裁决规定 RLS 写检查应判原始形态还是落库形态(检索式见下方 Prior rulings 行)。

协议声明 / 是否改协议: A、B、D 不改协议。C 要修订 ADR-0053(Tier H,维护者亲审)。

前提(每条带 re-check):

  • F7 仍在 main 上:git grep -n "function lteBound" origin/main -- packages/formula/src/matches-filter.ts,应命中 1 处。
  • RLS 写检查走 matchesFilterCondition:git grep -n "matchesFilterCondition" origin/main -- packages/plugins/plugin-security/src | head。
  • 仓内没有带日期 check 的现成策略(复查命令已改写,原命令的路径 glob 匹配 0 个文件,其零不是读数):git grep -n -E '\b(using|check)\s*:' origin/main -- examples —— 阳性对照:必中 examples/app-showcase/src/security/permission-sets.ts 的 using;判据:命中的 check 行里无 date/datetime/time 列。2026-10-01 总监席重测:2 条 using、1 条 check(owner == current_user.email),0 条日期型。

选项 × 真实代价

选项 做什么 客户可感知的后果
A RLS 写检查改判落库后的形态:判之前,先把写入镜像里声明为 date/datetime/time 的列,过一遍 @objectstack/core 的 temporalStorageForm(每个驱动写入时用的同一规则)。随后另开一张删除卡删掉 lteBound,它的 24 个直调用例改走 lowering。 无感:今天放行的写,届时仍放行。读和写对同一行判同一个答案。代价是 security 车道一张卡。
B RLS 编译接缝对 check 子句的 date 列也做下沉(对日期文本保序等价,using 读不受影响),然后删 lteBound。 日期列的写入无感。但 { $field, addDays } 这半没人兜,那类写入仍会被误拒。
C 保留 lteBound,把它声明为 F7 对写入镜像的永久规则。 零代码,但同一条规则永远存在两处。要修订 ADR-0053(第 5 条禁止永久副本),Tier H。
D 现在就删,接受收窄。 一个普通的 SDK 写法(Date 写日期字段、带日期 check)会被拒 403,而读能看到该行。这是公开入口上的读写不一致,偏严(fail-closed),不泄露数据。已实测。

业务含义直译:

  • A:门卫按入库后的样子查验,和仓库里登记的样子一致。
  • B:只修好最常见的日期写法,另一种写法仍可能被误拦。
  • C:两本规则手册永远并存。
  • D:先关门,有人能看见自己的东西,却改不了。

四轴(业务立场)

  • 项目长远合理性: A 让写检查判「将要存在的那一行」,第 9 条在 F7 上成立,删副本也就变成机械操作。两年后的样子:写检查与读看到的是同一种类型化数据。PostgreSQL 的 RLS WITH CHECK 就是在列类型转换之后判新行。B 是半量;C 让契约迁就实现;D 留下读写分歧。
  • 实际业务拉动: 今天仓内零个带日期 check 的策略(dev 实测),所以等 A 落地没有代价。但「SDK 用 Date 写日期字段,且有日期 check」是普通写法,D 的收窄会落在它身上。
  • 防 AI 犯错: 原始形态和落库形态之间的差异,作者看不见,AI 写策略时也看不见。A 从结构上消除这个差异。D 出错时是响亮的 403,但会让人误以为是策略写错了。
  • 创业阶段不扩散: A 是一张 security 车道的卡,不加门禁、不改 ADR。C 要修订 ADR,并永久维护两处规则。

os-decision-facets

  • ① 长远合理性:A 收敛到一条规则、一种形态,删除副本变为机械操作;C 扩大特例(永久副本),D 留下读写分歧。
  • ② 实际业务拉动:今天无仓内策略踩到;但 SDK 的 Date 写日期字段是常见形态,D 会让它在带日期 check 的策略下被拒。
  • ③ 防 AI 犯错:A 消除作者看不见的「原始 vs 落库」分歧;D 是响亮拒绝但会误导;C/B 保留隐性双规则或半条路径。
  • ④ 创业阶段不扩散:A 一张卡、无新门禁、无 ADR 修订;C 要 Tier H ADR 修订与永久维护。

Prior rulings read: "RLS check post-image stored form", "lteBound", "ADR-0053 D-D1 item 7 RLS check" → 0 hits beyond 5902355785 and ADR-0053 D-D1 itself; ADR-0053 D-D1 items 5, 7, 9; thread: #20822 (5926452254), seat 1's in-seat answers 5918373748 (A, the RLS twin for using).

推荐:A。 只看①选 A;②③④ 是否翻转:否(②零拉动只影响时序,A 落地前 F7 的副本照留,不回退)。

回退: 若 A 被否决,选 B,并另立一张卡兜 $field 那一半;⛔ 不选 D。

置信缺口:

  • 没有实测真实 Postgres / MySQL 上 time 列的写检查;
  • 没有实测 datetime 列的 ISO 带时区写法在 temporalStorageForm 下的边界;
  • 仓外的策略作者是否依赖今天的宽松读法,看不见。

裁后执行

相关


Generated by Claude Code

Activity

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

Metadata

Metadata

Assignees

Labels

area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsdomain:servicespriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions