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

No activity

Activity on this issue will appear here.

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